web-dev-qa-db-ja.com

unique_ptr引数をコンストラクタや関数に渡すにはどうすればいいですか?

私はC++ 11でセマンティクスを移動するのが初めてなので、コンストラクタまたは関数でunique_ptrパラメータを処理する方法をよく知りません。このクラスが自分自身を参照しているとします。

#include <memory>

class Base
{
  public:

    typedef unique_ptr<Base> UPtr;

    Base(){}
    Base(Base::UPtr n):next(std::move(n)){}

    virtual ~Base(){}

    void setNext(Base::UPtr n)
    {
      next = std::move(n);
    }

  protected :

    Base::UPtr next;

};

unique_ptr引数を取る関数を書くべきですか?

そして呼び出しコードでstd::moveを使う必要がありますか?

Base::UPtr b1;
Base::UPtr b2(new Base());

b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
362
codablank1

一意のポインターを引数として使用する方法と、それらに関連付けられた意味を以下に示します。

(A)値別

Base(std::unique_ptr<Base> n)
  : next(std::move(n)) {}

ユーザーがこれを呼び出すには、次のいずれかを実行する必要があります。

Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));

値で一意のポインタを取得するということは、問題の関数/オブジェクト/などへのポインタの所有権をtransferringすることを意味します。 newBaseが構築された後、nextBaseemptyであることが保証されます。オブジェクトを所有しておらず、そのオブジェクトへのポインタさえも持っていません。なくなった。

値によってパラメーターを取得するため、これは保証されます。 std::moveは実際にはmove何もしません。それはただ派手なキャストです。 std::move(nextBase)は、nextBaseへのr値参照であるBase&&を返します。それだけです。

Base::Base(std::unique_ptr<Base> n)はr値参照ではなく値で引数を取るため、C++は自動的に一時変数を作成します。 std::move(nextBase)を介して関数に与えたstd::unique_ptr<Base>からBase&&を作成します。このテンポラリの構築が、実際にnextBaseから関数引数nに値を移動することです。

(B)非定数l値参照による

Base(std::unique_ptr<Base> &n)
  : next(std::move(n)) {}

これは、実際のl値(名前付き変数)で呼び出す必要があります。次のような一時的に呼び出すことはできません:

Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.

この意味は、非const参照のその他の使用の意味と同じです。関数は、ポインターの所有権を主張することも、主張しないこともできます。このコードを考えます:

Base newBase(nextBase);

nextBaseが空であるという保証はありません。それは空かもしれません;できないかもしれません。 Base::Base(std::unique_ptr<Base> &n)が何をしたいかに本当に依存します。そのため、何が起こるかは関数のシグネチャだけではあまり明確ではありません。実装(または関連ドキュメント)を読む必要があります。

そのため、これをインターフェイスとして提案することはありません。

(C)const l-value参照による

Base(std::unique_ptr<Base> const &n);

const&から移動できないため、実装を示しません。 const&を渡すことにより、関数はポインターを介してBaseにアクセスできますが、どこにもstoreそれを格納できません。それの所有権を主張することはできません。

これは便利です。必ずしも特定のケースではありませんが、誰かにポインターを渡して、cannot(C++の規則を破らずに、const)所有権を主張します。彼らはそれを保存することはできません。彼らはそれを他の人に渡すことができますが、それらの他の人は同じルールを守らなければなりません。

(D)r値参照による

Base(std::unique_ptr<Base> &&n)
  : next(std::move(n)) {}

これは、「non-const l-value参照による」場合とほぼ同じです。違いは2つあります。

  1. あなたはcanを一時的に渡すことができます

    Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
    
  2. 非一時的な引数を渡す場合は、muststd::moveを使用する必要があります。

後者が本当に問題です。この行が表示される場合:

Base newBase(std::move(nextBase));

この行が完了した後、nextBaseは空になっているはずです。移動したはずです。結局、あなたはそのstd::moveをそこに置いて、動きが起こったことを伝えます。

問題はそうではないということです。移動したことは保証されていません。それはから移動された可能性がありますが、ソースコードを見ればわかるだけです。関数のシグネチャだけではわかりません。

推奨事項

  • (A)値による:関数がunique_ptrの所有権所有権を要求する場合は、値で取得します。
  • (C)const l-value reference:関数の実行中に関数が単にunique_ptrを使用する場合は、const&を使用します。または、&を使用する代わりに、const&またはunique_ptrを実際の型に渡します。
  • (D)r値参照による:関数が所有権を主張する場合と主張しない場合(内部コードパスに応じて)、&&で取得します。しかし、可能な限りこれを行うことは強くお勧めします。

Unique_ptrを操作する方法

unique_ptrをコピーすることはできません。移動のみが可能です。これを行う適切な方法は、std::move標準ライブラリ関数を使用することです。

unique_ptrを値で取得する場合、その値から自由に移動できます。ただし、std::moveが原因で実際​​に移動することはありません。次のステートメントを取ります。

std::unique_ptr<Base> newPtr(std::move(oldPtr));

これは実際には2つのステートメントです。

std::unique_ptr<Base> &&temporary = std::move(oldPtr);
std::unique_ptr<Base> newPtr(temporary);

(注:非一時的なr値参照は実際にはr値ではないため、上記のコードは技術的にコンパイルされません。これはデモのみを目的としています)。

temporaryは、oldPtrへの単なるr値参照です。 newPtrconstructorで動きが発生します。 unique_ptrの移動コンストラクター(&&をそれ自体に渡すコンストラクター)が実際の移動を行います。

unique_ptr値があり、それをどこかに保存したい場合、muststd::moveを使用して保存します。

782
Nicol Bolas

はい、コンストラクタで値でunique_ptrを取る場合はそうする必要があります。明白にいいことです。 unique_ptrはコピー不可(private copy ctor)なので、あなたが書いたものはコンパイラエラーになるはずです。

4
Xeo

編集:厳密に言えば、コードは機能しますが、この答えは間違っています。それについての議論はあまりにも有用であるので私はここにそれを残すだけです。この他の答えは、これを最後に編集した時点で得られた最良の答えです。 unique_ptr引数をコンストラクタまたは関数に渡すにはどうすればよいですか?

::std::moveの基本的な考え方は、あなたにunique_ptrを渡している人は、渡しているunique_ptrを知っているという知識を表現するためにそれを使うべきであるということです。

つまり、unique_ptr自体ではなく、unique_ptrへの右辺値参照をメソッド内で使用する必要があります。普通のunique_ptrを渡すにはコピーを作成する必要があり、unique_ptrのインターフェースでは明示的に禁止されているので、これはとにかくうまくいきません。興味深いことに、名前付きの右辺値参照を使用すると再び左辺値に変換されるので、::std::moveinside自分のメソッドも使用する必要があります。

これはあなたの2つの方法がこのように見えるべきであることを意味します:

Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability

void setNext(Base::UPtr &&n) { next = ::std::move(n); }

それから方法を使用している人々はこれをするでしょう:

Base::UPtr objptr{ new Base; }
Base::UPtr objptr2{ new Base; }
Base fred(::std::move(objptr)); // objptr now loses ownership
fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership

ご覧のとおり、::std::moveは、ポインタが所有権を失うことになるということを表しています。これは、最も関連性があり、知っておくと便利な場所にあるからです。これが目に見えないことが起こった場合、あなたのクラスを使っている人がobjptrが突然明白な理由もなく所有権を失うようにすることは非常に混乱するでしょう。

3
Omnifarious

上の投票に答えた。右辺値参照で渡すことを好みます。

右辺値参照による受け渡しに関する問題の原因は何かを理解しています。しかし、この問題を二つの側面に分けましょう。

  • 呼び出し元の場合

私はコードBase newBase(std::move(<lvalue>))またはBase newBase(<rvalue>)を書かなければなりません。

  • 呼び出し先の場合:

ライブラリの作成者は、所有権が必要な場合は、メンバーを初期化するためにunique_ptrを実際に移動することを保証する必要があります。

それで全部です。

右辺値参照で渡すと、1つの「移動」命令しか呼び出されませんが、値で渡すと2つになります。

うん、図書館の作者がこれについて専門的でないなら、彼はmemberを初期化するためにunique_ptrを動かさないかもしれない、しかしそれはあなたではなく作者の問題である。それが値または右辺値参照によって渡されるものは何でも、あなたのコードは同じです!

もしあなたがライブラリを書いているのなら、今それを保証すべきだということを知っているので、ただそれをしなさい、右辺値参照による受け渡しはvalueより良い選択です。あなたのライブラリを使用するクライアントはただ同じコードを書くでしょう。

さて、あなたの質問のために。 unique_ptr引数をコンストラクタや関数に渡すにはどうすればいいですか?

あなたは何が最良の選択であるか知っています。

http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html

0
merito
Base(Base::UPtr n):next(std::move(n)) {}

はるかに良いはずです

Base(Base::UPtr&& n):next(std::forward<Base::UPtr>(n)) {}

そして

void setNext(Base::UPtr n)

あるべき

void setNext(Base::UPtr&& n)

同じ体で。

そして... ... handle()evtとは何ですか?

0