web-dev-qa-db-ja.com

保証されたコピーの除外はどのように機能しますか?

2016年のOulu ISO C++標準会議で、 簡易値カテゴリによるコピー防止の保証 という提案が、標準委員会によってC++ 17に投票されました。

保証されたコピーの省略はどのように機能しますか?コピーの省略が既に許可されているいくつかのケースをカバーしていますか?それとも、コピーの省略を保証するためにコードの変更が必要ですか?

74
jotik

さまざまな状況下でコピーの削除が許可されました。ただし、たとえ許可されていたとしても、コピーが省略されていないかのようにコードが機能する必要がありました。つまり、アクセス可能なコピーおよび/または移動コンストラクターが必要でした。

保証されたコピーの省略は、コピー/ムーブを省略できる特定の状況が実際にコピー/ムーブを引き起こさないように、多くのC++の概念を再定義しますat all。コンパイラはコピーを削除していません。標準では、そのようなコピーは決して起こり得ないと述べています。

次の機能を検討してください。

T Func() {return T();}

非保証コピー除外ルールでは、これにより一時が作成され、その一時から関数の戻り値に移動します。その移動操作mayは省略されますが、Tは使用されない場合でもアクセス可能な移動コンストラクターを保持する必要があります。

同様に:

T t = Func();

これは、tのコピー初期化です。これにより、初期化tが戻り値Funcでコピーされます。ただし、Tには、呼び出されない場合でも、移動コンストラクターが必要です。

コピー省略の保証 prvalue式の意味を再定義 。 C++ 17より前のprvalueは一時オブジェクトです。 C++ 17では、prvalue式は単にmaterialize一時的なものですが、まだ一時的なものではありません。

Prvalueを使用してprvalueのタイプのオブジェクトを初期化する場合、一時はマテリアライズされません。 return T();を実行すると、prvalueを介して関数の戻り値が初期化されます。この関数はTを返すため、一時ファイルは作成されません。 prvalueの初期化は、単に戻り値を直接初期化します。

理解すべきことは、戻り値はprvalueなので、まだオブジェクトではないであるということです。 T()と同じように、オブジェクトの初期化子にすぎません。

T t = Func();を実行すると、戻り値のprvalueはオブジェクトtを直接初期化します。 「一時的なコピーと移動の作成」ステージはありません。 Func()の戻り値はT()と同等のprvalueであるため、T()を行ったかのように、tT t = T()によって直接初期化されます。

Prvalueが他の方法で使用される場合、prvalueは一時オブジェクトを具体化し、その式で使用されます(または、式がない場合は破棄されます)。したがって、const T &rt = Func();を実行すると、prvalueはテンポラリを実現し(初期化子としてT()を使用)、その参照は通常の一時的なライフタイム拡張機能と共にrtに格納されます。

Elisionが許可することを保証する1つのことは、動かないオブジェクトを返すことです。たとえば、lock_guardはコピーまたは移動できないため、値で返す関数を作成できません。しかし、コピーの削除が保証されていれば、できます。

エリシオンの保証は、直接初期化でも機能します。

new T(FactoryFunction());

FactoryFunctionが値でTを返す場合、この式は割り当てられたメモリに戻り値をコピーしません。代わりに、メモリを割り当て、関数呼び出しの戻り値メモリとして割り当てられたメモリを直接使用します。

そのため、値によって返されるファクトリ関数は、それについて知らなくても、ヒープに割り当てられたメモリを直接初期化できます。もちろん、これらの関数internallyが保証されたコピー省略のルールに従っている限り。タイプTのprvalueを返す必要があります。

もちろん、これも機能します:

new auto(FactoryFunction());

型名を書くのが嫌いな場合に。


上記の保証はprvaluesに対してのみ機能することを認識することが重要です。つまり、named変数を返すときに保証はありません。

T Func()
{
   T t = ...;
   ...
   return t;
}

この場合、tにはアクセス可能なコピー/移動コンストラクターが必要です。はい、コンパイラはコピー/移動を最適化することを選択できます。ただし、コンパイラは、アクセス可能なコピー/移動コンストラクターの存在を確認する必要があります。

したがって、名前付き戻り値の最適化(NRVO)については何も変わりません。

111
Nicol Bolas