提示するコードには、2 つのコピーが存在する可能性があります。result
変数から返されたオブジェクトまでの関数内の 1 つ。機能の複雑さが許せば、NRVO が対応します。単一の return ステートメントがあるように見える場合、コンパイラはそのコピーを省略します (コンパイラ フラグでそれを無効にしておらず、何らかの最適化レベルが有効になっていると仮定します)。
2 番目の潜在的なコピーは、戻り値から最終ストレージまでの呼び出し元にあります。そのコピーはほとんどの場合、コンパイラによって省略されます。ここでは、NRVO の場合よりもさらに簡単に行うことができます。呼び出し規約 (私が知っているすべての呼び出し規約) は、呼び出し元が返されたオブジェクト用のスペースを予約することを決定します。その場所への隠しポインタを関数に渡します。関数は、そのポインターを最初のコピーの宛先として使用します。
関数T f()
は に変換されvoid f( uninitialized<T>* __ret )
( のようなものはありませuninitialized<>
んが、ご容赦ください)、関数の実装では、return
ステートメントにコピーするときにそのポインターを宛先として使用します。呼び出し元のサイトで、コンパイラがある場合は、それを次T a = f();
のように変換しますT a/*no construction here*/; f(&a);
質問には、あなたが過去に誤解を招いたことを示す興味深いコードがあります: const list<uint32>& diff = getDiffNewElements(...)
. ( のように) 値を直接格納するのではなく、参照を使用しても、list<uint32> diff = getDiffNewElements(...)
作成されるコピーの数にはまったく影響しません。返されたオブジェクトにコピーgetDiffNewElements
(またはコピーを省略) する必要があり、そのオブジェクトは呼び出し元のスコープ内にあります。const 参照を取得することで、そのオブジェクトに直接名前を付けるのではなく、スコープ内の名前のないオブジェクトとして保持し、参照のみを使用することをコンパイラーに伝えます。意味的には、次のように変換できます。
T __unnamed = getDiffNewElements(...); // this copy can be elided
T const& diff = __unnamed;
コンパイラは無料であり、おそらく余分なスペースを必要とせずに識別子diff
をエイリアスとして使用して参照を最適化する__unnamed
ため、一般的には代替よりも悪くはありませんが、コードはわずかに複雑であり、利点はありませんまったく。
昔、時間があったときにブログを始めて、値セマンティクス、(N)RVO、コピー省略に関する記事をいくつか書きました。見てみるといいかもしれません。