[C++] Копировать конструктор для класса с unique_ptr


Answers

Попробуйте этот помощник для создания глубоких копий и справитесь, когда source unique_ptr имеет значение null.

    template< class T >
    std::unique_ptr<T> copy_unique(const std::unique_ptr<T>& source)
    {
        return source ? std::make_unique<T>(*source) : nullptr;
    }

Например:

class My
{
    My( const My& rhs )
        : member( copy_unique(rhs.member) )
    {
    }

    // ... other methods

private:
    std::unique_ptr<SomeType> member;
};
Question

Как реализовать конструктор копирования для класса с переменной-членом unique_ptr ? Я рассматриваю только C ++ 11.




Обычный случай, когда один должен иметь unique_ptr в классе, должен иметь возможность использовать наследование (иначе простой объект мог бы также работать, см. Раздел RAII). Для этого случая до сих пор нет соответствующего ответа в этой теме .

Итак, вот стартовая точка:

struct Base
{
    //some stuff
};

struct Derived : public Base
{
    //some stuff
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class
};

... и цель, как сказано, сделать Foo гибкой.

Для этого нужно сделать глубокую копию содержащегося указателя, чтобы гарантировать, что производный класс скопирован правильно.

Это можно сделать, добавив следующий код:

struct Base
{
    //some stuff

    auto clone() const { return std::unique_ptr<Base>(clone_impl()); }
protected:
    virtual Base* clone_impl() const = 0;
};

struct Derived : public Base
{
    //some stuff

protected:
    virtual Derived* clone_impl() const override { return new Derived(*this); };                                                 
};

struct Foo
{
    std::unique_ptr<Base> ptr;  //points to Derived or some other derived class

    //rule of five, but a user-defined dtor is not necessary due to unique_ptr
    Foo(Foo const& other) : ptr(other.ptr->clone()) {}
    Foo(Foo && other) = default;
    Foo& operator=(Foo const& other) { ptr = other.ptr->clone(); return *this; }
    Foo& operator=(Foo && other) = default;
};

Здесь в основном две вещи:

  • Во-первых, это добавление конструкторов копирования и перемещения, которые неявно удаляются в Foo поскольку конструктор копирования unique_ptr удаляется. Конструктор перемещения может быть добавлен просто = default ..., которое позволяет компилятору знать, что обычный конструктор перемещения не должен быть удален (это работает, поскольку unique_ptr уже имеет конструктор перемещения, который может быть использован в этом случае).

    Для конструктора копирования Foo нет аналогичного механизма, так как нет конструктора копий unique_ptr . Итак, нужно построить новый unique_ptr , заполнить его копией исходного указателя и использовать его как члена скопированного класса.

  • В случае, если наследование задействовано, копия оригинала должна быть сделана тщательно. Причина в том, что выполнение простой копии с помощью std::unique_ptr<Base>(*ptr) в приведенном выше коде приведет к разрезанию, т. Е. Только копируется базовый компонент объекта, а производная часть отсутствует.

    Чтобы этого избежать, копия должна быть выполнена с помощью шаблона клонирования. Идея состоит в том, чтобы сделать копию через виртуальную функцию clone_impl() которая возвращает Base* в базовом классе. Однако в производном классе он расширяется с помощью ковариации, чтобы возвращать Derived* , и этот указатель указывает на вновь созданную копию производного класса. Базовый класс может затем получить доступ к этому новому объекту через указатель базового класса Base* , обернуть его в unique_ptr и вернуть его через действительную функцию clone() которая вызывается извне.