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

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

Ответ 1

Поскольку unique_ptr не может использоваться совместно, вам необходимо либо глубоко скопировать его содержимое, либо преобразовать unique_ptr в shared_ptr.

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_( new int( i ) ) {}
   A( const A& a ) : up_( new int( *a.up_ ) ) {}
};

int main()
{
   A a( 42 );
   A b = a;
}

Вы можете, как упоминалось в NPE, использовать move-ctor вместо copy-ctor, но это приведет к различной семантике вашего класса. Перемещение-ctor должно было бы сделать элемент перемещаемым явно через std::move:

A( A&& a ) : up_( std::move( a.up_ ) ) {}

Наличие полного набора необходимых операторов также приводит к

A& operator=( const A& a )
{
   up_.reset( new int( *a.up_ ) );
   return *this,
}

A& operator=( A&& a )
{
   up_ = std::move( a.up_ );
   return *this,
}

Если вы хотите использовать свой класс в std::vector, вам в основном нужно решить, должен ли вектор быть единственным владельцем объекта, и в этом случае было бы достаточно сделать класс подвижным, но не скопированным. Если вы не укажете copy-ctor и copy-присваивание, компилятор будет определять способ использования std::vector с типами только для перемещения.

Ответ 2

Обычный случай для того, чтобы иметь 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, заполнить его копией оригинального pointee и использовать в качестве члена скопированного класса.

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

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

Ответ 3

Попробуйте этот помощник для создания глубоких копий и справитесь, когда 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;
};

Ответ 4

Даниэль Фрай упомянул о решении для копирования, я хотел бы поговорить о том, как переместить unique_ptr

#include <memory>
class A
{
  public:
    A() : a_(new int(33)) {}

    A(A &&data) : a_(std::move(data.a_))
    {
    }

    A& operator=(A &&data)
    {
      a_ = std::move(data.a_);
      return *this;
    }

  private:
    std::unique_ptr<int> a_;
};

Они называются конструктором перемещения и назначением перемещения

вы могли бы использовать их вот так

int main()
{
  A a;
  A b(std::move(a)); //this will call move constructor, transfer the resource of a to b

  A c;
  a = std::move(c); //this will call move assignment, transfer the resource of c to a

}

Вам нужно обернуть a и c с помощью std:: move, потому что у них есть имя std:: move сообщает компилятору преобразовать значение в rvalue, независимо от параметров В техническом смысле std:: move аналогичен "std:: rvalue"

После перемещения ресурс unique_ptr передается другому уникальному_ptr

Есть много тем, которые документируют ссылку rvalue; это довольно легко начать с.

Изменить:

Перемещенный объект должен оставаться действительным, но неопределенным состоянием.

С++ primer 5, ch13 также дает очень хорошее объяснение того, как "перемещать" объект

Ответ 5

Я предлагаю использовать make_unique

class A
{
   std::unique_ptr< int > up_;

public:
   A( int i ) : up_(std::make_unique<int>(i)) {}
   A( const A& a ) : up_(std::make_unique<int>(*a.up_)) {};

int main()
{
   A a( 42 );
   A b = a;
}

Ответ 6

unique_ptr не копируемый, это только подвижно.

Это напрямую повлияет на Test, который во втором примере также может быть перемещаемым и не копируемым.

На самом деле, это хорошо, что вы используете unique_ptr который защищает вас от большой ошибки.

Например, основная проблема с вашим первым кодом заключается в том, что указатель никогда не удаляется, что очень, очень плохо. Скажем, вы бы это исправили:

class Test
{
    int* ptr; // writing this in one line is meh, not sure if even standard C++

    Test() : ptr(new int(10)) {}
    ~Test() {delete ptr;}
};

int main()
{       
     Test o;
     Test t = o;
}

Это тоже плохо. Что произойдет, если вы скопируете Test? Будет два класса, у которых есть указатель, указывающий на один и тот же адрес.

Когда один Test уничтожен, он также уничтожит указатель. Когда ваш второй Test будет уничтожен, он также попытается удалить память за указателем. Но он уже был удален, и мы получим некоторую ошибку во время выполнения доступа к памяти (или неопределенное поведение, если нам не повезет).

Таким образом, правильный путь - реализовать конструктор копирования и оператор присваивания копии, чтобы поведение было понятным, и мы могли создать копию.

unique_ptr далеко впереди нас здесь. Это имеет смысловой смысл: " Я unique, поэтому вы не можете просто скопировать меня". Таким образом, это предотвращает нас от ошибки при реализации операторов под рукой.

Вы можете определить конструктор копирования и оператор назначения копирования для особого поведения, и ваш код будет работать. Но вы, по праву (!), Вынуждены это сделать.

Мораль этой истории: всегда используйте unique_ptr в подобных ситуациях.