unique_ptr
私は C++11 の移動セマンティクスを初めて使い、コンストラクタや関数のパラメータをどのように処理するかよく分かりません。自分自身を参照する次のクラスを考えてみましょう。
#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?
ベストアンサー1
以下に、一意のポインタを引数として取る方法と、それに関連する意味を示します。
(A) 価値による
Base(std::unique_ptr<Base> n)
: next(std::move(n)) {}
ユーザーがこれを呼び出すには、次のいずれかを実行する必要があります。
Base newBase(std::move(nextBase));
Base fromTemp(std::unique_ptr<Base>(new Base(...));
一意のポインタを値で取得するということは、ポインタの所有権を問題の関数/オブジェクトなどに転送するnewBase
ことを意味します。が構築された後、は空でnextBase
あることが保証されます。オブジェクトを所有しておらず、それへのポインタさえもうありません。それは消えてしまいます。
これは、パラメータを値で受け取るため保証されます。 はstd::move
実際には何も移動しません。これは単なる派手なキャストです。への右辺値参照である をstd::move(nextBase)
返します。それが行うことのすべてです。Base&&
nextBase
は右辺値参照ではなく値によって引数を取るためBase::Base(std::unique_ptr<Base> n)
、C++ は自動的に一時 を構築します。 は、を介して関数に渡したstd::unique_ptr<Base>
から を作成します。この一時 の構築によって、実際に からの値が関数引数 に移動します。Base&&
std::move(nextBase)
nextBase
n
(B) 非定数左辺値参照
Base(std::unique_ptr<Base> &n)
: next(std::move(n)) {}
これは実際の左辺値 (名前付き変数) で呼び出す必要があります。次のように一時的に呼び出すことはできません。
Base newBase(std::unique_ptr<Base>(new Base)); //Illegal in this case.
この意味は、他の非定数参照の使用と同じです。関数はポインタの所有権を主張する場合と主張しない場合があります。次のコードがあるとします。
Base newBase(nextBase);
空であるという保証はありませんnextBase
。空になるかもしれませんし、空でないかもしれません。それは本当に何をBase::Base(std::unique_ptr<Base> &n)
したいかによって決まります。そのため、関数のシグネチャからだけでは何が起こるかは明らかではありません。実装 (または関連するドキュメント) を読む必要があります。
そのため、私はこれをインターフェースとしてはお勧めしません。
(C) 定数左辺値参照による
Base(std::unique_ptr<Base> const &n);
から移動できないため、実装は示しませんconst&
。 を渡すことでconst&
、関数はBase
ポインターを介して にアクセスできますが、どこにも保存できないことを意味します。 の所有権を主張することはできません。
これは便利です。必ずしもあなたの特定のケースに当てはまるわけではありませんが、誰かにポインターを渡すときに、その人がそのポインターの所有権を主張できない(キャストできないなどの C++ のルールに違反しない限りconst
) ことを知っておくことは常に良いことです。ポインターは保存できません。他の人に渡すことはできますが、他の人も同じルールに従う必要があります。
(D) r値参照による
Base(std::unique_ptr<Base> &&n)
: next(std::move(n)) {}
これは、「非定数左辺値参照」の場合とほぼ同じです。違いは 2 つあります。
一時的に渡すことができます:
Base newBase(std::unique_ptr<Base>(new Base)); //legal now..
一時的でない引数を渡す場合はを使用する必要があります。
std::move
後者が本当に問題です。次の行が表示された場合:
Base newBase(std::move(nextBase));
この行が完了したら、空になるはずだと当然予想できnextBase
ます。この行は移動されているはずです。結局のところ、std::move
そこにあるのが移動が発生したことを知らせてくれるのです。
問題は、それが移動されていないことです。移動されたかどうかは保証されません。移動された可能性はありますが、ソース コードを見なければわかりません。関数のシグネチャだけではわかりません。
推奨事項
- (A) 値渡し:関数が の所有権を主張することを意味している場合は
unique_ptr
、値渡しします。 - (C) const 左辺値参照による:関数が
unique_ptr
実行中の間だけ を使用するだけの場合は、 を使用しますconst&
。または、を使用するのではなく、実際に指し示される型に&
またはを渡します。const&
unique_ptr
- (D) 右辺値参照による:関数が所有権を主張できる場合とできない場合があります (内部コード パスによって異なります)。 によって所有権を取得します
&&
。ただし、可能な限りこれを行わないことを強くお勧めします。
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
内で発生します。の移動コンストラクタ ( を自身に受け取るコンストラクタ) が、実際の移動を実行します。newPtr
unique_ptr
&&
値がありunique_ptr
、それをどこかに保存したい場合は、を使用して保存する必要があります。std::move