unique_ptr
メンバー変数を持つクラスのコピーコンストラクターを実装するにはどうすればよいですか? C++ 11のみを検討しています。
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は、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
をコピー可能にすることです。
このためには、派生クラスが正しくコピーされるように、含まれるポインターのdeep copyを行う必要があります。
これは次のコードを追加することで実現できます:
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
~Foo() = default;
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;
};
ここでは、基本的に2つのことが行われています。
1つ目は、コピーコンストラクターと移動コンストラクターの追加です。これらは、unique_ptr
のコピーコンストラクターが削除されると、Foo
で暗黙的に削除されます。移動コンストラクターは、単に= default
...で追加できます。これは、通常の移動コンストラクターがnotであることをコンパイラーに知らせるだけです。削除(これは機能します。unique_ptr
には既にこの場合に使用できる移動コンストラクターがあるためです)。
Foo
のコピーコンストラクターには、unique_ptr
のコピーコンストラクターがないため、同様のメカニズムはありません。そのため、新しいunique_ptr
を作成し、元の指示先のコピーでそれを埋め、コピーされたクラスのメンバーとして使用する必要があります。
継承が関係する場合は、元の指示先のコピーを慎重に行う必要があります。その理由は、上記のコードでstd::unique_ptr<Base>(*ptr)
を介して単純なコピーを行うと、スライスが発生するためです。つまり、派生部分はなく、オブジェクトのベースコンポーネントのみがコピーされます。
これを回避するには、複製パターンを介してコピーを行う必要があります。考え方は、基本クラスでBase*
を返す仮想関数clone_impl()
を介してコピーを行うことです。ただし、派生クラスでは、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 :: rvalue "
移動後、unique_ptrのリソースは別のunique_ptrに転送されます
右辺値参照を文書化する多くのトピックがあります。 これは非常に簡単なことです 。
編集:
移動されたオブジェクト 有効であるが指定されていない状態のまま 。
C++入門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;
}