Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Auto-cloning unique_ptr

std::unique_ptr has a deleted copy constructor, which means that if you have a unique_ptr in your class Foo as a data member then you must write your own copy constructor for Foo and manually deep-copy that member (even if the compiler-generated copy constructor would be fine for all other members).

In order to be able to copy in a polymorphic way, the clone() method pattern can be used. Let's assume our objects have a clone method like this:

class Base {
    virtual std::unique_ptr<Base> clone() = 0;
};

Foo looks like this now:

class Foo {
public:
    ...
    Foo(Foo const& other)
        : b(other.b->clone())
        , // init 10 more members that could otherwise be auto-copied just fine
          // with the automatically generated copy constructor
    {}
    ...

private:
    std::unique_ptr<Base> b;
    //10 more data members

};

Now, I found a way to auto-clone Foo::b, by writing a wrapper over unique_ptr that defines the copy constructor and assignment by calling clone.

template <typename T>
class auto_cloned_unique_ptr
{
private:
    std::unique_ptr<T> up;

public:
    // copy constructor
    auto_cloned_unique_ptr(auto_cloned_unique_ptr<T> const& other)
        : up(other.up->clone()) {}

    // copy assignment
    auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T> const& other)
    {
        this->up = other.up->clone();
        return *this;
    }

    auto_cloned_unique_ptr(std::unique_ptr<T> _up)
        : up(std::move(_up)) {}

    // Delegate everything else to unique_ptr
    auto_cloned_unique_ptr(auto_cloned_unique_ptr<T>&& other)
        : up(std::move(other.up)) {}

    auto_cloned_unique_ptr<T>& operator =(auto_cloned_unique_ptr<T>&& other)
    {
        this->up = std::move(other.up);
        return *this;
    }

    auto operator *() const {return *up;}
    auto operator->() const {return up.operator->();}
    auto get() -> const {return up.get();}

};

Now if we use this we don't need to define our own copy constructor:

class Foo2 {
public:
    ...

private:
    auto_cloned_unique_ptr<Base> b;
    //10 more data members

};

Is such an approach very much frowned upon (for using a non-standard wrapper over unique_ptr)?

like image 868
isarandi Avatar asked May 18 '14 19:05

isarandi


2 Answers

Let me first paraphrase what you want to do: You want that each instance of Foo has its own instance of Base in b; in particular, if you copy a Foo, the copy will have its own new Base, initially with the same "value". In other words, Base should behave like a value.

At the same time, you can't store Base directly in Foo because it is an abstract class. In other words, you want b to be polymorphic.

There you have it: you want a polymorphic value. Other people have recognized this need and proposed for C++20 as polymorphic_value<Base>. From the documentation:

The class template, polymorphic_value, confers value-like semantics on a free-store allocated object. A polymorphic_value may hold an object of a class publicly derived from T, and copying the polymorphic_value will copy the object of the derived type.

It has a reference implementation that you can use as of now. Very simply put, it is an wrapper around std::unique_ptr similar to what you propose.

like image 112
ingomueller.net Avatar answered Nov 16 '22 04:11

ingomueller.net


The problem with your approach is that it is changing the meaning of a unique_ptr. The key thing about a unique_ptr is that it tells who is the owner of an object. If you add a copy constructor for unique_ptr, what does that mean? Are you copying the ownership? A and B both uniquely own the thing? That does not make sense. If they share ownership, then you should be using the shared_ptr to indicate the shared ownership. If you want to have a unique owner of a copy of the object, you would naturally indicate that by make_unique(*pFoo). With base and derived objects, having the base object have a
virtual unique_ptr<Foo> Clone() const=0;
is a perfectly normal construct. That is, the derived classes know how to copy themselves so they don't produce a sliced copy, but they return a unique_ptr to the base class to indicate that you will own the copy they have produced. Within these clone operations, yes, you will need to explicitly handle non-copyable members of the derived classes, so you won't be able to just use a default or generated copy constructor. You need to answer "what does it mean to copy something that contains this thing that can't be copied?"
As a concrete example, what would it mean to copy a derived class that had a mutex? What if it were locked and another thread were waiting on it? See why it's hard to give a general answer?

like image 37
CHKingsley Avatar answered Nov 16 '22 03:11

CHKingsley