Автоматическое клонирование unique_ptr

std::unique_ptr имеет конструктор удаленной копии, что означает, что если у вас есть unique_ptr в вашем классе Foo как член данных, тогда вы должны написать свой собственный конструктор копирования для Foo и вручную глубоко скопировать член (даже если создатель, созданный компилятором, будет хорош для всех остальных членов).

Чтобы иметь возможность копировать полиморфным способом, можно использовать шаблон метода clone(). Пусть предположим, что у наших объектов есть метод клонирования:

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

Теперь Foo выглядит следующим образом:

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

};

Теперь я нашел способ авто-клонирования Foo::b, написав обертку поверх unique_ptr, которая определяет конструктор и назначение копии, вызывая 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();}

};

Теперь, если мы используем это, нам не нужно определять наш собственный конструктор копирования:

class Foo2 {
public:
    ...

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

};

Является ли такой подход очень неодобрительным (для использования нестандартной обертки над unique_ptr)?

Ответ 1

Проблема с вашим подходом заключается в том, что он меняет значение unique_ptr. Главное в unique_ptr заключается в том, что он сообщает, кто является владельцем объекта. Если вы добавите конструктор копирования для unique_ptr, что это значит? Вы копируете право собственности? А и В оба однозначно владеют этим? Это не имеет смысла. Если они разделяют право собственности, вы должны использовать shared_ptr для указания совместного владения. Если вы хотите иметь уникального владельца копии объекта, вы, естественно, должны указать, что make_unique (* pFoo). С базовыми и производными объектами, имеющих базовый объект, есть   virtual unique_ptr <Foo> Clone() const = 0;
является вполне нормальной конструкцией. То есть производные классы умеют копировать себя, поэтому они не производят фрагментированную копию, но возвращают unique_ptr в базовый класс, чтобы указать, что вы будете иметь копию, которую они создали. Внутри этих операций клонирования да, вам нужно будет явно обрабатывать не скопируемые члены производных классов, поэтому вы не сможете просто использовать созданный по умолчанию или сгенерированный конструктор копии. Вам нужно ответить "что значит копировать что-то, что содержит эту вещь, которую нельзя скопировать?"
Как конкретный пример, что бы это означало для копирования производного класса, который имел мьютекс? Что, если бы он был заблокирован, и на него ждала еще одна нить? Смотрите, почему трудно дать общий ответ?

Ответ 2

Этот подход прекрасен, но вы должны быть очень осторожны, чтобы не клонировать ваши объекты, когда вы этого не хотели.

Также наследование от unique_ptr может повысить производительность