私は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?
一意のポインターを引数として使用する方法と、それらに関連付けられた意味を以下に示します。
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
が構築された後、nextBase
はemptyであることが保証されます。オブジェクトを所有しておらず、そのオブジェクトへのポインタさえも持っていません。なくなった。
値によってパラメーターを取得するため、これは保証されます。 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
に値を移動することです。
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)
が何をしたいかに本当に依存します。そのため、何が起こるかは関数のシグネチャだけではあまり明確ではありません。実装(または関連ドキュメント)を読む必要があります。
そのため、これをインターフェイスとして提案することはありません。
Base(std::unique_ptr<Base> const &n);
const&
から移動できないため、実装を示しません。 const&
を渡すことにより、関数はポインターを介してBase
にアクセスできますが、どこにもstoreそれを格納できません。それの所有権を主張することはできません。
これは便利です。必ずしも特定のケースではありませんが、誰かにポインターを渡して、cannot(C++の規則を破らずに、const
)所有権を主張します。彼らはそれを保存することはできません。彼らはそれを他の人に渡すことができますが、それらの他の人は同じルールを守らなければなりません。
Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}
これは、「non-const l-value参照による」場合とほぼ同じです。違いは2つあります。
あなたはcanを一時的に渡すことができます
Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
非一時的な引数を渡す場合は、muststd::move
を使用する必要があります。
後者が本当に問題です。この行が表示される場合:
Base newBase(std::move(nextBase));
この行が完了した後、nextBase
は空になっているはずです。移動したはずです。結局、あなたはそのstd::move
をそこに置いて、動きが起こったことを伝えます。
問題はそうではないということです。移動したことは保証されていません。それはから移動された可能性がありますが、ソースコードを見ればわかるだけです。関数のシグネチャだけではわかりません。
unique_ptr
の所有権所有権を要求する場合は、値で取得します。unique_ptr
を使用する場合は、const&
を使用します。または、&
を使用する代わりに、const&
または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値参照です。 newPtr
のconstructorで動きが発生します。 unique_ptr
の移動コンストラクター(&&
をそれ自体に渡すコンストラクター)が実際の移動を行います。
unique_ptr
値があり、それをどこかに保存したい場合、muststd::move
を使用して保存します。
はい、コンストラクタで値でunique_ptr
を取る場合はそうする必要があります。明白にいいことです。 unique_ptr
はコピー不可(private copy ctor)なので、あなたが書いたものはコンパイラエラーになるはずです。
編集:厳密に言えば、コードは機能しますが、この答えは間違っています。それについての議論はあまりにも有用であるので私はここにそれを残すだけです。この他の答えは、これを最後に編集した時点で得られた最良の答えです。 unique_ptr引数をコンストラクタまたは関数に渡すにはどうすればよいですか?
::std::move
の基本的な考え方は、あなたにunique_ptr
を渡している人は、渡しているunique_ptr
を知っているという知識を表現するためにそれを使うべきであるということです。
つまり、unique_ptr
自体ではなく、unique_ptr
への右辺値参照をメソッド内で使用する必要があります。普通のunique_ptr
を渡すにはコピーを作成する必要があり、unique_ptr
のインターフェースでは明示的に禁止されているので、これはとにかくうまくいきません。興味深いことに、名前付きの右辺値参照を使用すると再び左辺値に変換されるので、::std::move
inside自分のメソッドも使用する必要があります。
これはあなたの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
が突然明白な理由もなく所有権を失うようにすることは非常に混乱するでしょう。
上の投票に答えた。右辺値参照で渡すことを好みます。
右辺値参照による受け渡しに関する問題の原因は何かを理解しています。しかし、この問題を二つの側面に分けましょう。
私はコード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
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
とは何ですか?