Andrey Pro
Andrey Pro

Reputation: 501

Deep copy constructor with std::vector of smart pointers

Let's say I have a class FooContainer that aggregates unique_ptr objects of type Foo

#include <vector>
#include <memory>

class FooContainer
{
protected:
std::vector<std::unique_ptr<Foo>> many;
//other attributes
public:
FooCoontainer(const FooContainer&);
//handling functions for Foo
};

The question is how to correctly implement deep copy constructor, and what is syntax for it. Simply assigning

FooContainer::FooContainer(const FooContainer& fc)
{
many=fc.many;

}

will attempt to copy the pointers and will be (thankfully) disallowed by the compiler for unique_ptr. so I would need to do something like this

FooContainer::FooContainer(const FooContainer& fc)
{
many.reserve(fc.many.size());
for(int i=0;i<fc.many.size();i++)
many.emplace_back(new Foo(*fc.many[i]));//assume that Foo has a copy constructor
}

Is this the way to do it? Or may be I should use shared_ptr instead of unique_ptr?

I also have an additional question.
The reason to go for smart pointers (and also for protected in the code above) is that I have derived class BarContainer that aggregates objects Bar in many, which are in turn subclass of Foo. Since the the handling of Bar is very similar to Foo this approach will allow to save a lot of duplicate code compared to two separate classes.

However,. the copy constructor of the BarContainer is problematic. It will the call copy constructor of FooContainer, that will go agead and copy only the Foo part instead of whole Bar. even worse, any invocation of the virtual methods of Bar will call the version of Foo. So I need a way to override this behaviour.Making the copy constructor virtual is not possible. Also the copy constructor of Bar could discard the result of Foo copy constructor and to dperform correct copying, but this is quite inefficient

So what is the best solution for this problem?

Upvotes: 4

Views: 4969

Answers (1)

aschepler
aschepler

Reputation: 72356

Or may be I should use shared_ptr instead of unique_ptr?

That depends on whether you require deep copies or are okay with shallow copies (meaning changes to one will also be visible in the other).

However,. the copy constructor of the BarContainer is problematic. It will the call copy constructor of FooContainer, that will go agead and copy only the Foo part instead of whole Bar.

The usual fix is to give your base class a virtual method clone:

class Foo {
public:
    Foo(Foo&&) = default;
    Foo& operator=(Foo&&) = default;
    virtual ~Foo() = 0;
    virtual std::unique_ptr<Foo> clone() const = 0;

protected: // or public if appropriate
    Foo(const Foo&);
    Foo& operator=(const Foo&);
};

class Bar : public Foo {
public:
    virtual std::unique_ptr<Foo> clone() const;
};

std::unique_ptr<Foo> Bar::clone() const {
    return make_unique<Bar>(*this);
}

If Foo is not abstract, it would also have an actual implementation of clone().

FooContainer::FooContainer(const FooContainer& fc)
{
    many.reserve(fc.many.size());
    for (auto const& fptr : fc.many)
        many.emplace_back(fptr->clone());
}

I've used a template function make_unique, which was accidentally forgotten from the C++11 Standard, but will be official soon. If your compiler doesn't have one, it's simple to put your own in some header file:

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&& ... args) {
    return std::unique_ptr<T>( new T(std::forward<Args>(args)...) );
}

(Together, unique_ptr, make_unique, shared_ptr, make_shared, and vector finish the huge language improvement meaning you'll almost never need the low-level and dangerous new or delete keywords again.)

Upvotes: 3

Related Questions