2016 年のオウル ISO C++ 標準会議では、単純化された値のカテゴリによる保証されたコピー省略と呼ばれる提案が、標準委員会によって C++17 に投票されました。
保証されたコピー省略はどのように機能しますか? コピーの省略がすでに許可されているいくつかのケースをカバーしていますか、またはコピーの省略を保証するためにコードの変更が必要ですか?
2016 年のオウル ISO C++ 標準会議では、単純化された値のカテゴリによる保証されたコピー省略と呼ばれる提案が、標準委員会によって C++17 に投票されました。
保証されたコピー省略はどのように機能しますか? コピーの省略がすでに許可されているいくつかのケースをカバーしていますか、またはコピーの省略を保証するためにコードの変更が必要ですか?
コピー省略は、多くの状況下で発生することが許可されていました。ただし、許可されたとしても、コピーが省略されていないかのようにコードが機能する必要がありました。つまり、アクセス可能なコピーおよび/または移動コンストラクターが必要でした。
保証されたコピー省略は、いくつかの C++ 概念を再定義します。これにより、コピー/移動を省略できる特定の状況で、実際にはコピー/移動がまったく引き起こされなくなります。コンパイラはコピーを省略していません。標準では、そのようなコピーは決して起こり得ないと述べています。
この関数を考えてみましょう:
T Func() {return T();}
保証されていないコピー省略規則の下では、これにより一時が作成され、その一時から関数の戻り値に移動されます。その移動操作は省略T
できますが、使用されない場合でも、アクセス可能な移動コンストラクターが必要です。
同様に:
T t = Func();
のコピー初期化ですt
。t
これはの戻り値で初期化をコピーしますFunc
。ただし、T
呼び出されない場合でも、移動コンストラクターが必要です。
保証されたコピーの省略により、prvalue 式の意味が再定義されます。C++17 より前では、prvalues は一時オブジェクトです。C++17 では、prvalue 式は単に一時的なものを具体化できるものですが、まだ一時的なものではありません。
prvalue を使用して prvalue の型のオブジェクトを初期化すると、一時オブジェクトは具体化されません。を実行するとreturn T();
、prvalue を介して関数の戻り値が初期化されます。その関数は を返すためT
、一時的なものは作成されません。prvalue の初期化は、戻り値を直接初期化するだけです。
理解しておくべきことは、戻り値は prvalue であるため、まだオブジェクトではないということです。これは、オブジェクトのイニシャライザにすぎませんT()
。
その場合T t = Func();
、戻り値の prvalue はオブジェクトを直接初期化しますt
。「一時的なものを作成してコピー/移動する」段階はありません。Func()
の戻り値は と同等の prvalue であるため、T()
によってt
直接初期化さT()
れT t = T()
ます。
prvalue が他の方法で使用される場合、prvalue は一時オブジェクトを実体化し、その式で使用されます (式がない場合は破棄されます)。したがってconst T &rt = Func();
、そうすると、prvalue は (イニシャライザとして使用して) 一時的なものを具体化し、その参照は通常の一時的な有効期間の拡張機能と共に にT()
格納されます。rt
保証された省略により実行できることの 1 つは、不動のオブジェクトを返すことです。たとえば、lock_guard
コピーまたは移動できないため、値で返す関数を使用できませんでした。ただし、コピーの省略が保証されている場合は可能です。
保証された省略は、直接の初期化でも機能します。
new T(FactoryFunction());
値によってFactoryFunction
返さT
れる場合、この式は戻り値を割り当てられたメモリにコピーしません。代わりにメモリを割り当て、割り当てられたメモリを関数呼び出しの戻り値メモリとして直接使用します。
したがって、値によって返されるファクトリ関数は、ヒープに割り当てられたメモリを知らないうちに直接初期化できます。もちろん、これらの関数が内部的に保証されたコピー省略のルールに従っている限り。タイプの prvalue を返す必要がありT
ます。
もちろん、これも機能します。
new auto(FactoryFunction());
型名を書きたくない場合。
上記の保証は prvalues に対してのみ機能することを認識することが重要です。つまり、名前付き変数を返すときに保証は得られません。
T Func()
{
T t = ...;
...
return t;
}
この場合t
でも、アクセス可能なコピー/移動コンストラクターが必要です。はい、コンパイラはコピー/移動を最適化することを選択できます。ただし、コンパイラは、アクセス可能なコピー/移動コンストラクターの存在を確認する必要があります。
したがって、名前付き戻り値の最適化 (NRVO) については何も変わりません。