std::move
をいつ使用するべきか、いつコンパイラーを最適化する必要があるのかがわかりません...例えば:
using SerialBuffer = vector< unsigned char >;
// let compiler optimize it
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
// Return Value Optimization
return buffer;
}
// explicit move
SerialBuffer read( size_t size ) const
{
SerialBuffer buffer( size );
read( begin( buffer ), end( buffer ) );
return move( buffer );
}
どちらを使うべきですか?
最初の方法のみを使用してください:
Foo f()
{
Foo result;
mangle(result);
return result;
}
これにより、already移動コンストラクターが使用可能であれば、その使用が許可されます。実際、コピーの省略が許可されている場合、ローカル変数はreturn
ステートメントの右辺値参照に正確にバインドできます。
2番目のバージョンでは、コピーの削除が積極的に禁止されています。最初のバージョンは普遍的に優れています。
すべての戻り値は既にmoved
であるか、最適化されているため、戻り値で明示的に移動する必要はありません。
コンパイラーは、(コピーを最適化するために)戻り値を自動的に移動し、移動を最適化することさえできます!
n3337標準ドラフトのセクション12.8(C++ 11):
特定の基準が満たされると、オブジェクトのコピー/移動コンストラクターおよび/またはデストラクターに副作用がある場合でも、実装はクラスオブジェクトのコピー/移動構築を省略することができます。そのような場合、実装は、省略されたコピー/移動操作のソースとターゲットを同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は、2つのオブジェクトがcopy elisionと呼ばれるこのコピー/移動操作の省略は、次の状況で許可されます(複数のコピーを削除するために組み合わせることができます) ):
[...]
例:
class Thing { public: Thing(); ~Thing(); Thing(const Thing&); }; Thing f() { Thing t; return t; } Thing t2 = f();
ここで、除外の基準を組み合わせて、クラス
Thing
のコピーコンストラクターへの2つの呼び出しを排除できます。ローカル自動オブジェクトt
を、関数f()
の戻り値の一時オブジェクトにコピーし、その一時オブジェクトをオブジェクトt2
にコピーします。事実上、ローカルオブジェクトt
の構築は、グローバルオブジェクトt2
を直接初期化するものと見なすことができ、そのオブジェクトの破壊はプログラムの終了時に発生します。Thing
に移動コンストラクターを追加しても同じ効果がありますが、一時オブジェクトからt2
への移動構成が省略されます。 —end example]ソースオブジェクトが関数パラメーターであり、コピーされるオブジェクトが左辺値で指定されているという事実を除いて、コピー操作の省略の基準が満たされるか満たされる場合、コピーのコンストラクターを選択するためのオーバーロード解決はオブジェクトが右辺値で指定されているかのように最初に実行されました。オーバーロードの解決が失敗した場合、または選択したコンストラクターの最初のパラメーターの型がオブジェクトの型への右辺値参照(おそらくcv修飾)でない場合、オブジェクトを左辺値と見なして、オーバーロードの解決が再度実行されます。
とても簡単です。
return buffer;
これを行うと、NRVOが発生するか、発生しません。そうでない場合は、buffer
name__が移動されます。
return std::move( buffer );
これを行うと、NVRO しないが発生し、buffer
name__が移動されます。
そのため、ここでstd::move
を使用しても得られるものはなく、失うものも多くあります。
この規則には1つの例外があります。
Buffer read(Buffer&& buffer) {
//...
return std::move( buffer );
}
buffer
name__が右辺値参照である場合、std::move
を使用する必要があります。これは、参照がNRVOに適格ではないため、std::move
なしでは左辺値からのコピーになるためです。
これは「常にmove
name__右辺値参照およびforward
name__ユニバーサル参照」ルールの単なるインスタンスであり、「move
name__戻り値を使用しない」ルールよりも優先されます。
ローカル変数を返す場合は、move()
を使用しないでください。これにより、コンパイラーはNRVOを使用できますが、失敗すると、コンパイラーは引き続き移動を許可されます(ローカル変数はreturn
ステートメント内でR値になります)。そのコンテキストでmove()
を使用すると、単にNRVOが禁止され、コンパイラーに強制的に移動(または移動が使用できない場合はコピー)が使用されます。ローカル変数以外のものを返す場合、とにかくNRVOはオプションではないので、オブジェクトを盗むつもりなら(そしてその場合のみ)move()
を使用する必要があります。