unique_ptr
メンバー変数を持つクラスのコピーコンストラクターを実装するにはどうすればよいですか?C ++ 11のみを検討しています。
std::vector
。
unique_ptr
目標がデータをに配置することである場合、おそらくを含むクラスのコピーコンストラクターは必要ありませんstd::vector
。一方、C ++ 11標準ではムーブコンストラクターが自動的に作成されているため、コピーコンストラクターが必要な場合があります...
unique_ptr
メンバー変数を持つクラスのコピーコンストラクターを実装するにはどうすればよいですか?C ++ 11のみを検討しています。
std::vector
。
unique_ptr
目標がデータをに配置することである場合、おそらくを含むクラスのコピーコンストラクターは必要ありませんstd::vector
。一方、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をmove-onlyタイプで使用する方法を案内します。
int
。unique_ptr<Base>
を格納するがある場合Derived
、上記はスライスされます。
A( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
value_ptr
- unique_ptr
プラスデリータ/複写機情報。
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
~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つ目は、コピーおよび移動コンストラクターの追加です。これは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
、直接の包含がそうでないときに指し示されるオブジェクトをコピーしたい理由を示唆することはありません。答え???継承。
unique_ptr
れるoptional
場合があります。
clone_impl
インベースを実装しても、派生クラスでそれを忘れてもコンパイラは通知しません。ただし、別の基本クラスCloneable
を使用して、clone_impl
そこに純粋な仮想を実装することもできます。次に、派生クラスで忘れた場合、コンパイラーは文句を言います。
このヘルパーを試してディープコピーを作成し、ソースの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;
};
Daniel Freyがコピーソリューションについて言及し、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は、パラメーターが何であれ、値を右辺値参照に変換するようコンパイラーに指示するためです。技術的には、std :: moveは、「 std :: rvalue "
移動後、unique_ptrのリソースは別のunique_ptrに転送されます
右辺値参照を文書化する多くのトピックがあります。これは、を始めるのが非常に簡単です。
編集:
移動されたオブジェクトは有効なままですが、指定されていない状態です。
C ++プライマー5、ch13もオブジェクトを「移動」する方法について非常に良い説明を提供します
a
では、b
移動コンストラクタでstd :: move(a)を呼び出した後にオブジェクトはどうなりますか?それは完全に無効ですか?
unique_ptr
コピー可能ではなく、移動のみ可能です。
これはTestに直接影響します。これは、2番目の例では、移動のみが可能でコピーは不可能です。
実際、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
か?同じアドレスを指すポインタを持つ2つのクラスがあります。
1つTest
が破壊されると、ポインタも破壊されます。2番目Test
が破壊されると、ポインタの背後にあるメモリも削除しようとします。しかし、それはすでに削除されており、不正なメモリアクセスランタイムエラー(または、運が悪ければ未定義の動作)が発生します。
したがって、正しい方法は、コピーコンストラクターとコピー代入演算子を実装することです。これにより、動作が明確になり、コピーを作成できます。
unique_ptr
ここで私たちよりずっと先です。それは意味的な意味を持っています:「私はunique
なので、あなたは私をただコピーすることはできません。」それで、今手元にあるオペレーターを実装するというミスから私たちを防ぎます。
特別な動作のためにコピーコンストラクターとコピー代入演算子を定義でき、コードが機能します。しかし、あなたは、当然のことながら(!)、そうすることを余儀なくされています。
ストーリーの教訓:常にunique_ptr
このような状況で使用してください。