1. ホーム
  2. c++

[解決済み] コピー保証のエリシオンはどのように機能しますか?

2023-01-12 03:41:48

質問

2016年のOulu ISO C++標準化会議において、以下のような提案がなされました。 簡略化された値カテゴリによるコピーエリジョンの保証 は、標準化委員会によって C++17 に投票されました。

コピー除去の保証は具体的にどのように機能するのでしょうか。コピー除去がすでに許可されていたいくつかのケースをカバーするのでしょうか、それともコピー除去を保証するためにコードの変更が必要なのでしょうか。

どのように解決するのですか?

コピー除去は多くの状況下で許可されていました。しかし、たとえそれが許可されていたとしても、コードはコピーがエリジョンされていないかのように動作できなければなりません。つまり、アクセス可能なコピーおよび/または移動コンストラクタがなければなりませんでした。

保証されたコピー消去は、コピー/移動が消去される可能性のある特定の状況が実際にはコピー/移動を引き起こさないように、多くの C++ 概念を再定義します。 まったく . コンパイラーはコピーを消去しているのではなく、標準ではそのようなコピーは決して起こり得ないと言っているのです。

この関数を考えてみましょう。

T Func() {return T();}

非保証のコピー消去ルールの下では、これは一時的なものを作成し、その一時的なものから関数の戻り値に移動することになります。その移動操作 はエリジオンになりますが T は、たとえそれが使われなくても、アクセス可能な移動コンストラクタを持たなければなりません。

同様に

T t = Func();

のコピー初期化です。 t . これは、コピー初期化 t の戻り値で Func . しかし T は、たとえそれが呼び出されないとしても、移動コンストラクタを持たなければなりません。

コピーエリジョンを保証する は prvalue 式の意味を再定義します。 . C++17 以前では、prvalue は一時的なオブジェクトです。C++17 では、prvalue 式は単に 実体化 一時的なものですが、まだ一時的なものではありません。

prvalueを使用してprvalueの型のオブジェクトを初期化した場合、テンポラリーはマテリアライズされません。あなたが return T(); を実行すると、これはprvalueを介して関数の戻り値を初期化します。その関数が返すので T を返すので、一時的なものは作成されず、prvalueの初期化は単に直接戻り値を初期化します。

理解すべきことは、戻り値がprvalueであるため、それは オブジェクトではなく であるということです。これは単にオブジェクトのイニシャライザであり、ちょうど T() と同じです。

をしたとき T t = Func(); を実行すると、戻り値の prvalue が直接オブジェクトを初期化します。 t というように、一時的な作成とコピー・移動の段階はありません。したがって Func() と等価な値を返します。 T() , t が直接初期化されるのは T() を行った場合と全く同じように T t = T() .

もしprvalueが他の方法で使われた場合、prvalueは一時的なオブジェクトを実体化し、その式で使われます(または式がない場合は捨てられます)。ですから、もしあなたが const T &rt = Func(); とすれば、prvalue は一時的なオブジェクトを実体化します(using T() をイニシャライザーとして使い)、その参照は rt に格納され、通常の一時的な寿命延長のものと一緒に格納されます。

保証されたエリシオンでできることのひとつは、動かないオブジェクトを返すことです。例えば lock_guard はコピーも移動もできないので、それを値で返す関数は作れませんでした。しかし、保証されたコピー消去を使用すると、それが可能になります。

保証されたエリジョンは、直接の初期化でも機能します。

new T(FactoryFunction());

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

つまり、値で返すファクトリー関数は、ヒープに割り当てられたメモリを意識することなく直接初期化することができるのです。そのため、これらの関数が 内部的に が保証されたコピー消去のルールに従う限りは、もちろん、です。の型のprvalueを返さなければなりません。 T .

もちろん、これも有効です。

new auto(FactoryFunction());

型名を書くのが嫌な人のために。


上記の保証はprvalueに対してのみ機能することを認識することが重要です。つまり、prvalueを返した場合は何も保証されません。 という名前の という名前の変数を返す場合は保証されません。

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

この例では t はまだアクセス可能なコピー/移動コンストラクタを持たなければなりません。そうです、コンパイラはコピー/移動を最適化しないように選択することができます。しかし、コンパイラはアクセス可能なコピー/移動のコンストラクタの存在を確認する必要があります。

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