HiveBrain v1.2.0
Get Started
← Back to all entries
snippetcppMinor

How to design interface of deep-copy behaving pointer containers?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
designbehavinginterfacepointerdeephowcontainerscopy

Problem

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 Container : public std::vector >
{
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 
using Container = std::vector >;

template 
Container defaultAllocate(int nItems);

template 
Container deepCopy(const Container& other);


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

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


instead of:

class Big;
Container someContainer(12);
Container 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.

Solution

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
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(5,7);
}

Code Snippets

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);
}

Context

StackExchange Code Review Q#26111, answer score: 2

Revisions (0)

No revisions yet.