web-dev-qa-db-ja.com

c ++ 11戻り値の最適化または移動?

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 );
}

どちらを使うべきですか?

157
elvis.dukaj

最初の方法のみを使用してください:

Foo f()
{
  Foo result;
  mangle(result);
  return result;
}

これにより、already移動コンストラクターが使用可能であれば、その使用が許可されます。実際、コピーの省略が許可されている場合、ローカル変数はreturnステートメントの右辺値参照に正確にバインドできます。

2番目のバージョンでは、コピーの削除が積極的に禁止されています。最初のバージョンは普遍的に優れています。

97
Kerrek SB

すべての戻り値は既に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修飾)でない場合、オブジェクトを左辺値と見なして、オーバーロードの解決が再度実行されます。

114
Jamin Grey

とても簡単です。

return buffer;

これを行うと、NRVOが発生するか、発生しません。そうでない場合は、buffername__が移動されます。

return std::move( buffer );

これを行うと、NVRO しないが発生し、buffername__が移動されます。

そのため、ここでstd::moveを使用しても得られるものはなく、失うものも多くあります。

この規則には1つの例外があります。

Buffer read(Buffer&& buffer) {
    //...
    return std::move( buffer );
}

buffername__が右辺値参照である場合、std::moveを使用する必要があります。これは、参照がNRVOに適格ではないため、std::moveなしでは左辺値からのコピーになるためです。

これは「常にmovename__右辺値参照およびforwardname__ユニバーサル参照」ルールの単なるインスタンスであり、「movename__戻り値を使用しない」ルールよりも優先されます。

43
Oktalist

ローカル変数を返す場合は、move()を使用しないでください。これにより、コンパイラーはNRVOを使用できますが、失敗すると、コンパイラーは引き続き移動を許可されます(ローカル変数はreturnステートメント内でR値になります)。そのコンテキストでmove()を使用すると、単にNRVOが禁止され、コンパイラーに強制的に移動(または移動が使用できない場合はコピー)が使用されます。ローカル変数以外のものを返す場合、とにかくNRVOはオプションではないので、オブジェクトを盗むつもりなら(そしてその場合のみ)move()を使用する必要があります。

24