Конструктор копирования для класса с уникальным ПТР


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

5 74

5 ответов:

С 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, но это приведет к другой семантике вашего класса. Move-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-assignment, компилятор будет указывать вам, как использовать std::vector с типами только для перемещения.

обычный случай для одного, чтобы иметь 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() который возвращает a Base* в базовом классе. Однако в производном классе он расширяется через ковариацию до возвратить Derived*, и этот указатель указывает на вновь созданный экземпляр производного класса. Затем базовый класс может получить доступ к этому новому объекту через указатель базового класса Base*, оберните его в unique_ptr, и вернуть его через фактический clone() функция, которая вызывается извне.

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

Даниэль Фрей упомянул о решении для копирования, я бы рассказал о том, как переместить 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 переносится на другой unique_ptr

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

Edit:

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

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

Я предлагаю использовать 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;
}