
Is the following design valid, and if so, would it make a useful addition to the Boost library? I enclose a draft implementation, and look forward to any comments. /Christian - - - It can quite often be of interest to convert a program that uses a direct container into using an indirect container of smart pointers to the objects instead, either just temporarily to see what happens to performance, or permanently if it turns out to be beneficial. Unfortunately, this normally means that the program has to be modified in many different places, even for programs that are not affected as such by the slightly different semantics that indirect containers have. The proxy indirect container header file makes it possible to switch between direct and indirect containers by changing a single typedef in any such program. For example, the following test program was modified to use an indirect vector instead of a direct one by just changing the type declaration from std::vector<person> to proxy_vector<person>::self. //=================================================================== #include <boost/operators.hpp> #include "proxy.hpp" int calls_to_copy_constructor = 0; //=================================================================== class person : boost::totally_ordered<person> //------------------------------------------------------------------- { public: string name; int age; person() {} person(const string& in_name, int in_age); person(const person& other) : name(other.name), age(other.age) { ++calls_to_copy_constructor; } // Compares first name, then age friend bool operator== (const person& a, const person& b); friend bool operator< (const person& a, const person& b); bool read(istream& in_file); }; ostream& operator<< (ostream& out_file, const person& x); //=================================================================== int main (int argc, char* argv[]) //------------------------------------------------------------------- { person a_person; person someone_special("Alice", 27); ifstream in_file("test_proxy.in"); // typedef std::vector<person> buf_type; // <---- typedef proxy_vector<person>::self buf_type; // <---- buf_type buf(5, person("Undefined", -1)); buf.front() = person("First", 1); *(buf.begin() + 1) = person("Second", 2); buf[2] = person("Third", 3); (buf.begin() + 3)->name = "Incomplete"; while (a_person.read(in_file)) { buf.push_back(a_person); } std::sort(buf.begin(), buf.end()); for (buf_type::iterator it = buf.begin(); it != buf.end(); ++it) { cout << *it; if (it->age < 0) cout << " Not a proper age"; if (*it == someone_special) cout << " Someone special"; if (it != buf.begin() && (*it == *(it – 1))) cout << " Duplicate record"; cout << endl; } buf_type::iterator end_it = std::unique(buf.begin(), buf.end()); cout << "\nNumber of unique records: " << (end_it - buf.begin()); cout << "\n\nCalls to 'person's copy constructor: " << calls_to_copy_constructor << endl; return 0; } //=================================================================== So how does this work? First of all, the declaration proxy_vector<T>::self is short for proxy_container< std::vector< proxy<T> > > There are convenience templates like this for the basic container types in STL, but by using the general form the proxy container adaptor can be used on any container that has an STL compliant interface. Class proxy At the very center of the design is the proxy template class. A proxy<T> object is a smart pointer that has a boost::shared_ptr<T> as its only data member. It constructs, assigns, and takes responsibility for deleting the object in the same way as a shared_ptr. It does not support any pointer arithmetic, however, so the ++ and -- operators are undefined. Without pointer arithmetic, there is very little use for the comparison operators as they are defined for shared_ptr, so these are instead given the semantics of element_compare, which means that they compare the two objects as such rather than the pointers. (There is a conversion function to shared_ptr that can be used to test the pointers as such if necessary.) With these definitions, we can use the STL algorithms directly on any Container< proxy<T> >, and they will produce the same results as they would for a Container<T>. This applies both to algorithms like std::sort, which depends on the < operator for comparisons, and algorithms like unique, which relies on the == operator. The proxy class also contains an implicit conversion operator from proxy<T> to T&. This means that we can use a proxy<T> as the actual parameter to any function that expects a T for input or output, or, to put it another way, that the proxy is automatically “cashed in” for a T object as soon as this is required. In particular, this means that we can copy T objects from a Container< proxy<T> > into T variables, in exactly the same way as if it had been a Container<T>, since the proxy<T> object that the iterator delivers will be automatically converted to a T if it is assigned to a T variable. If we assign it to a proxy<T> variable, it will of course remain a proxy. To handle assignments to proxy variables, the default assignment operator is supplemented by operator=(const T&). This operator will create a proxy that points at a new copy of the T object, and assign it to the left hand variable. This means that we now have a Container< proxy<T> > that behaves like a Container<T> for comparisons, and when we retrieve whole objects from the container. Class proxy_iterator To get operator-> working in the same way as for a Container<T>, we define the classes proxy_iterator and const_proxy_iterator. A proxy_iterator is publicly derived from the iterator of the underlying Container< proxy<T> >. It works just like standard iterator in all respects except one, which is that operator-> does a double dereferencing, so that it will deliver a pointer to the T object itself. This lets us use the -> operator in the same way that we would with a direct container. operator* is not changed, however, and retains its single dereferencing semantics. Are we really allowed to define these operators like this? --Yes, we are. While it is true that this means that the semantics will be different for (*x).y and x->y, this does not affect the STL algorithms in any way, since they don’t make any use of the -> operator. (How could they, when they’re supposed to work on the built-in types as well?) The proxy iterators can be mixed freely with the underlying standard iterators, since the derived-to-base-class conversion will convert in one direction, and the proxy iterators have have a conversion constructor that converts in the other direction. Since the compiler will prefer a derived-to-base conversion over a user-defined conversion, the ambiguity problems that are normally associated with having two types that can be implicitly converted in both directions are eliminated. Class proxy_container The class proxy_container, finally, inherits from the underlying Container< proxy<T> > and overrides the definitions for iterator and const_iterator so that the proxy iterators are used instead. It also redefines the standard container member functions that return an iterator so that they return the appropriate proxy iterator. In order to be able to insert T objects directly into the proxy container, the class proxy_container also redefines all members that insert an object into the container so that there are two versions of each function: one that takes a proxy<T> as input and inserts a new proxy that points at the same object, and one that takes a T object and inserts a proxy that points at a newly created copy of the object. Taken together, these classes let us emulate the interface of a Container<T> with something that is actually an indirect container. Since it is only a single type declaration that has to be changed to switch between direct and indirect containers, it becomes practical to do this for testing purposes. The indirect containers still retain the full interface of a traditional container of smart pointers, so the additional capabilities of an indirect container are available in the usual way. - - - I have uploaded the full source code for the draft implementation, as well as and the test program shown here, to the files section at Yahoo, and I have tested it with MSVC6 and gcc 3.3 on Windows XP. For MSVC6 there is the problem that the implementation of std::vector::iterator is just a plain pointer and not a standards compliant iterator, so proxy_vector uses a deque rather than a vector as a workaround. File proxy.hpp: (slightly abridged) //================================================================== // 20-feb-2004 Christian Engström (christian.engstrom@glindra.org) //------------------------------------------------------------------ #include <vector> #include <deque> #include <list> #include <boost/smart_ptr.hpp> //================================================================== // 'proxy' //------------------------------------------------------------------ template <class T> class proxy { private: typedef proxy<T> self; boost::shared_ptr<T> m_sp; public: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Types typedef T element_type; // "Inherited" from 'boost::shared_ptr' typedef T value_type; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors proxy() {} template<class Y> proxy(const proxy<Y>& other) : m_sp(other.as_shared_ptr()) {} template<class Y> explicit proxy(const boost::shared_ptr<Y>& other) : m_sp(other) {} template<class Y> explicit proxy(const boost::weak_ptr<Y>& other) : m_sp(other) {} template<class Y> explicit proxy(std::auto_ptr<Y>& other) : m_sp(other) {} template<class Y> explicit proxy(Y* other) : m_sp(other) {} // Assignment self& operator=(const self& other) {m_sp = other.m_sp; return *this;} self& operator=(const T& other) {*this = proxy<T>(new T(other)); return *this;} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Conversions operator T&() const {return **this;} // Implicit conversion to T bool is_null() const {return !m_sp;} boost::shared_ptr<T>& as_shared_ptr() {return m_sp;} const boost::shared_ptr<T>& as_shared_ptr() const {return m_sp;} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Comparisons are made on the value, never the pointer. bool operator== (const self& other) const {return **this == *other;} bool operator!= (const self& other) const {return **this != *other;} bool operator< (const self& other) const {return **this < *other;} bool operator> (const self& other) const {return **this > *other;} bool operator<= (const self& other) const {return **this <= *other;} bool operator>= (const self& other) const {return **this >= *other;} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Dereferencing T& operator*() const {return *m_sp;} T* operator->() const {return m_sp.get();} T* get() const {return m_sp.get();} T* as_ptr() const {return m_sp.get();} }; //================================================================== // 'proxy_iterator' //------------------------------------------------------------------ template <class MutableBaseIterator> class proxy_iterator : public MutableBaseIterator { private: typedef proxy_iterator<MutableBaseIterator> self; typedef MutableBaseIterator base; typedef typename base::value_type::element_type T; public: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Types typedef typename base::difference_type difference_type; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors proxy_iterator() {} proxy_iterator(const MutableBaseIterator& it) : base(it) {} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Standard operators for iterators T* operator-> () {return (**this).get();} // Indirect self& operator++ () {base::operator++ (); return *this;} self& operator-- () {base::operator-- (); return *this;} self operator++ (int) {return base::operator++ (0);} self operator-- (int) {return base::operator-- (0);} self& operator+= (difference_type n) {base::operator+= (n); return *this;} self& operator-= (difference_type n) {base::operator-= (n); return *this;} self operator+ (difference_type n) const {return base::operator+ (n);} self operator- (difference_type n) const {return base::operator- (n);} friend self operator+ (difference_type n, self x) {return x + n;} friend difference_type operator- (self x, self y) {return base(x) - base(y);} }; //================================================================== // 'const_proxy_iterator' //------------------------------------------------------------------ { // Analagous to proxy_iterator, and with a conversion constructor from proxy_iterator. ... }; //================================================================== // 'proxy_container' //------------------------------------------------------------------ template <class BaseProxyContainer> class proxy_container : public BaseProxyContainer { private: typedef proxy_container self; typedef BaseProxyContainer base; typedef typename BaseProxyContainer::value_type value_type; typedef typename BaseProxyContainer::value_type::element_type T; public: // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Constructors proxy_container() {} proxy_container(const base& c) : base(c) {} template <class InputIterator> proxy_container(InputIterator first, InputIterator last) : base(first, last) {} explicit proxy_container(typename base::size_type n, const value_type& x = value_type()) : base(n, x) {} proxy_container(typename base::size_type n, const T& x) { for (typename base::size_type i = 0; i < n; ++i) { push_back(proxy<T>(new T(x))); } } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Iterators redefined typedef proxy_iterator<typename base::iterator> iterator; typedef const_proxy_iterator<typename base::iterator, typename base::const_iterator> const_iterator; typedef std::reverse_iterator<iterator> reverse_iterator; typedef std::reverse_iterator<const_iterator> const_reverse_iterator; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Container members that either return an iterator or take a value_type parameter (or both) iterator begin() {return base::begin();} const_iterator begin() const {return base::begin();} iterator end() {return base::end();} const_iterator end() const {return base::end();} reverse_iterator rbegin() {return base::rbegin();} const_reverse_iterator rbegin() const {return base::rbegin();} reverse_iterator rend() {return base::rend();} const_reverse_iterator rend() const {return base::rend();} void push_front(const value_type& x) {base::push_front(x);} void push_front(const T& x) {base::push_front(proxy<T>(new T(x)));} void push_back(const value_type& x) {base::push_back(x);} void push_back(const T& x) {base::push_back(proxy<T>(new T(x)));} iterator insert(iterator position, const value_type& x) {return base::insert(x);} iterator insert(iterator position, const T& x) {return base::insert(position, proxy<T>(new T(x)));} void insert(iterator position, typename base::size_type n, const value_type& x) {base::insert(position, n, x);} void insert(iterator position, typename base::size_type n, const T& x) ; // Not yet implemented template<typename InputIterator> void insert(iterator position, InputIterator first, InputIterator last) {base::insert(position, first, last);} iterator erase(iterator position) {return base::erase(position);} iterator erase(iterator first, iterator last) {return base::erase(first, last);} void resize(typename base::size_type new_size, const value_type& x) {base::resize(new_size, x);} void resize(typename base::size_type new_size, const T& x) ; // Not yet implemented // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Special list operations void remove(const value_type& value) {base::remove(value);} void remove(const T& value); // Not yet implemented }; //================================================================== // The standard contaiers //------------------------------------------------------------------ template <class T> struct proxy_vector #if !defined(BOOST_MSVC_STD_ITERATOR) {typedef proxy_container< std::vector< proxy<T> > > self;}; #else {typedef proxy_container< std::deque< proxy<T> > > self;}; // MSVC workaround #endif template <class T> struct proxy_deque {typedef proxy_container< std::deque< proxy<T> > > self;}; template <class T> struct proxy_list {typedef proxy_container< std::list< proxy<T> > > self;}; // Associative containers are not yet implemented