unique_ptrを使用してクラスのコンストラクタをコピーする


105

unique_ptrメンバー変数を持つクラスのコピーコンストラクターを実装するにはどうすればよいですか?C ++ 11のみを検討しています。


9
さて、あなたはコピーコンストラクタに何をさせたいですか?
Nicol Bolas 2013

unique_ptrはコピーできないと読みました。これは、unique_ptrメンバー変数を持つクラスをどのように使用するのか不思議に思いますstd::vector
codefx 2013

2
@AbhijitKadam unique_ptrのコンテンツの詳細なコピーを作成できます。実際、それはしばしば賢明なことです。
キュービック

2
間違った質問をしている可能性があることに注意してください。unique_ptr目標がデータをに配置することである場合、おそらくを含むクラスのコピーコンストラクターは必要ありませんstd::vector。一方、C ++ 11標準ではムーブコンストラクターが自動的に作成されているため、コピーコンストラクターが必要な場合があります...
Yakk-Adam Nevraumont

3
@codefxベクトル要素はコピー可能である必要はありません。これは、ベクターをコピーできないことを意味します。
MM

回答:


81

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タイプで使用する方法を案内します。


4
moveコンストラクタに言及する価値はありますか?
NPE 2013

4
+1。ただし、移動コンストラクターはさらに強調する必要があります。コメントで、OPは、オブジェクトをベクトルで使用することを目標としています。そのために必要なのは、移動構築と移動割り当てだけです。
jogojapan 2013

36
警告として、上記の戦略はのような単純な型に対して機能しますintunique_ptr<Base>を格納するがある場合Derived、上記はスライスされます。
Yakk-Adam Nevraumont 2013

5
nullのチェックは行われないため、nullptrの逆参照が可能です。どうですかA( const A& a ) : up_( a.up_ ? new int( *a.up_ ) : nullptr) {}
Ryan Haining 14

1
@Aaronは、ポリモーフィックな状況では、なんらかの方法でタイプ消去されるか、無意味になります(削除するタイプがわかっている場合は、なぜ削除者だけを変更するのですか?)。いずれの場合も、はい、これはデザインであるvalue_ptr- unique_ptrプラスデリータ/複写機情報。
Yakk-Adam Nevraumont、2015

46

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()関数を介して返すことができます。


3
これは受け入れられるべき答えでした。他のすべての人がこのスレッドで円を描いて進んでいるのですがunique_ptr、直接の包含がそうでないときに指し示されるオブジェクトをコピーしたい理由を示唆することはありません。答え???継承
Tanveer Badar 2018

4
さまざまな理由から、具体的な型がポイントされていることがわかっている場合でも、unique_ptrを使用している可能性があります。1。nullにする必要があります。2.指示先が非常に大きく、スタックスペースが限られている場合があります。多くの場合、(1)と(2)は一緒に使用されます。そのため、null許容型よりも優先さunique_ptrれるoptional場合があります。
Ponkadoodle 2018年

3
にきびイディオムは別の理由です。
emsr 2018

基本クラスが抽象であってはならない場合はどうなりますか?純粋な指定子なしでそのままにしておくと、派生での再実装を忘れると、ランタイムバグが発生する可能性があります。
Oleksij Plotnyc'kyj

1
@ OleksijPlotnyc'kyj:はい、clone_implインベースを実装しても、派生クラスでそれを忘れてもコンパイラは通知しません。ただし、別の基本クラスCloneableを使用して、clone_implそこに純粋な仮想を実装することもできます。次に、派生クラスで忘れた場合、コンパイラーは文句を言います。
davidhigh

11

このヘルパーを試してディープコピーを作成し、ソースの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;
};

2
ソースがTから派生したものを指している場合、正しくコピーされますか?
ローマシャポバロフ2016

3
@RomanShapovalovいいえ、おそらくそうではありません。スライスされます。その場合、解決策はおそらく、仮想のunique_ptr <T> clone()メソッドをT型に追加し、Tから派生した型でclone()メソッドのオーバーライドを提供することです。クローンメソッドは、派生型とそれを返します。
Scott Langham

ディープコピー機能が組み込まれているc ++またはboostライブラリに一意のスコープ付きポインターはありませんか?ディープコピーの動作が必要な場合がよくあるので、これらのスマートポインターを使用するクラスのカスタムコピーコンストラクターなどを作成する必要がないと便利です。ただ疑問に思っています。
shadow_map 2016

5

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もオブジェクトを「移動」する方法について非常に良い説明を提供します


1
aでは、b移動コンストラクタでstd :: move(a)を呼び出した後にオブジェクトはどうなりますか?それは完全に無効ですか?
David Doria

3

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;
}

-1

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このような状況で使用してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.