だから、あなたが持っているとしましょう:
A compute()
{
A v;
…
return v;
}
そして、あなたはやっています:
A a = compute();
この式には 2 つの転送 (コピーまたは移動) が含まれます。最初v
に、関数内で によって示されるオブジェクトを関数の結果、つまりcompute()
式によって渡された値に転送する必要があります。これを Transfer 1 と呼びましょう。次に、この一時オブジェクトを転送して、a
Transfer 2 で示されるオブジェクトを作成します。
多くの場合、Transfer 1 と 2 の両方をコンパイラで省略できます。オブジェクトv
は の場所に直接構築され、a
転送は必要ありません。この例では、返されるオブジェクトが名前付きであるため、コンパイラは転送 1 の名前付き戻り値の最適化を使用する必要があります。ただし、コピー/移動省略を無効にすると、各転送には、A のコピー コンストラクターまたはその移動コンストラクターの呼び出しが含まれます。最近のほとんどのコンパイラでは、コンパイラはv
それが破棄されようとしていることを認識し、最初にそれを戻り値に移動します。次に、この一時的な戻り値が に移動されa
ます。A
移動コンストラクターがない場合は、代わりに両方の転送でコピーされます。
次に見てみましょう:
A compute(A&& v)
{
return v;
}
返される値は、関数に渡される参照から取得されます。コンパイラはv
、それが一時的なものであり、そこから移動しても問題ないと想定しているだけではありません1。この場合、転送 1 はコピーになります。その後、Transfer 2 が移動になります。返される値はまだ一時的なものなので問題ありません (参照は返されませんでした)。しかし、移動元のオブジェクトを取得したことがわかっv
ているため、パラメーターは右辺値参照であるため、次のようにコンパイラーに一時的なものとして扱うように明示的に指示できstd::move
ます。
A compute(A&& v)
{
return std::move(v);
}
これで、Transfer 1 と Transfer 2 の両方が Move になります。
1v
コンパイラがとして定義された を右辺値として自動的に扱わない理由はA&&
、安全上の理由の 1 つです。それを理解するのはあまりにもばかげているだけではありません。オブジェクトに名前が付けられると、コード全体で何度も参照できます。検討:
A compute(A&& a)
{
doSomething(a);
doSomethingElse(a);
}
a
が自動的に右辺値として扱われる場合doSomething
、その内臓を自由に引き裂くことができます。つまり、a
渡されたdoSomethingElse
ものが無効になる可能性があります。doSomething
引数を値で受け取ったとしても、オブジェクトは移動されるため、次の行では無効になります。この問題を回避するために、名前付き右辺値参照は左辺値です。つまりdoSomething
、呼び出されたときa
に、左辺値参照だけで取得されていない場合は、最悪でもコピーされます。次の行でも有効です。
compute
「わかりました。この値が一時的なオブジェクトであることは確かにわかっているので、この値を移動できるようにします」と言うのは、の作成者次第です。と言ってこれを行いますstd::move(a)
。たとえばdoSomething
、コピーを提供doSomethingElse
してから、そこから移動できるようにすることができます。
A compute(A&& a)
{
doSomething(a);
doSomethingElse(std::move(a));
}