3
\$\begingroup\$

I want to make a container which manages big objects. which performs deep copies on copy construction and copy assignment. I also like the interface of the std containers, so I wanted to implement it using public inheritance:

template <class TBigObject>
class Container : public std::vector< std::shared_ptr<TBigObject> >
{
public:
    Container(int nToAllocate){ /* fill with default constructed TBigObjects */}
    Container(const Container& other){ /* deep copy */ }
    Container(Container&&) = default;
    Container& operator = (const Container& population){ /* deep copy */ }
    Container& operator = (Container&&) = default;
};

I heard of the "Thou shalt not inherit from std containers" maxim and the reasons behind it. So I decided to make an alternative:

template <class T>
using Container = std::vector< std::shared_ptr<T> >;

template <class T>
Container<T> defaultAllocate(int nItems);

template <class T>
Container<T> deepCopy(const Container<T>& other);

This looks more expressive, but using the code feels strange and verbose:

class Big;
Container<Big> someContainer = defaultAllocate(12);
Container<Big> copy = deepCopy(someContainer);

instead of:

class Big;
Container<Big> someContainer(12);
Container<Big> copy = someContainer;

I am a beginner programmer and I don't want to make errors early on. I would like to ask your advice on which choice to make. Or even better, if there is a third option.

\$\endgroup\$
2
\$\begingroup\$

I think you are going about it the wrong way.

Containers already perform copy on copy construction/assignment because they are designed for value objects not pointers. What you really want is a wrapper object for pointers that performs a deep copy when that object is copied.

This has the added advantage of working with all containers.

If you define the move operators then it also works well with re-size operations as the move semantics will be applied to the wrapper when the the container is re-sized.

The other advantage here is you can make the wrapper behave like the underlying type. So you get easy access to all the algorithms.

template<typename T>
class Deep
{
   T*     value;
   public:
       Deep():                      value(NULL)         {}
       // Take ownership in this case.
       Deep(T const* value):        value(value)        {}
       Deep(T const& value):        value(new T(value)) {}
       ~Deep()                      {delete value;}
       Deep(Deep&& move)            {std::swap(value, move.value);}
       Deep(Deep const& copy)       {T* tmp = new T(*(copy.value));swap(tmp,value);delete tmp;}
       // Note this does do a deep copy via copy and swap
       Deep& operator=(Deep copy)   {std::swap(value, copy.value);return *this;}
       Deep& operator=(Deep&& move) {std::swap(value, move.value);return *this;}

       // Note: undefined behavior if used when value is NULL.
       //       You may want to add logic here depending on use case.
       operator T&()                {return *value;}
       operator T const&() const    {return *value;}

       T* release()                 {T* tmp = value;value = NULL;return tmp;}
};
int main()
{
    std::vector<Deep<int>>   deep(5,7);
}
\$\endgroup\$
  • \$\begingroup\$ Thank you! I am going to have to read this many times to fully understand. It looks like a really great concept. So this template is like an automatically dereferenced smart pointer? Is this a popularly known idiom? \$\endgroup\$ – Martin Drozdik May 16 '13 at 13:51
  • 1
    \$\begingroup\$ @MartinDrozdik: Its more like a copying smart pointer (in terms of resource management). There is not one in the standard as in general you don't want to deep copy pointer (that is why you are using pointers to avoid the copy). \$\endgroup\$ – Martin York May 16 '13 at 16:11
  • 1
    \$\begingroup\$ @MartinDrozdik: The auto de-referencing part is really useful technique unto itself. In this context it makes algorithms really easy to use (as you can use the underlying object with manually de-referencing it (this plays nicely with algorithms that expect values not pointers)). Note: This is why you need a constructor that works by value so the algorithms can put stuff back into the container, and it automatically gets wrapped. \$\endgroup\$ – Martin York May 16 '13 at 16:13
1
\$\begingroup\$

For starters, you talk about deepcopy then make collection of shared pointers. It's more than confusing.

If you mean DC, that is what you shall do. You can start by creating a proper smart pointer. Most of it is quite easy to do -- The one UI use have the same interface as auto_ptr without the templated ctors. The tricky part is the copy itself, that the pointer must do on its copy, but really must use some external force. Here you can either fix some strategy, like using the copy ctor of the object, or make it configurable. Mine uses a simple static policy class that is template parameter, but you can also make it a template function that the clients can specialize, or think up something else.

Once you have your dc_ptr, you can just use vestor or any collection of it as it fits all the requirements. And the semantics make sense too: copy of the collection will be completely separate from the original.

The fly in the ointment is you can have pointers in NULL state, for good or ill. If that is not desireable, you have to write your own interface for the collection that prevents this situation, and say replaces nulls with default-constructed objects or something.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.