コピー省略

C++コンピュータ プログラミングでは、コピー省略とは、オブジェクトの不要なコピーを排除するコンパイラ最適化手法を指します。

C++ 言語標準では、一般に実装によるあらゆる最適化が許可されていますが、その結果得られるプログラムの観察可能な動作が、つまりプログラムが標準で要求されているとおりに正確に実行されたと仮定した場合と同じである場合に限られます。さらに、標準では、コピーを省略できる状況もいくつか説明されており、最も一般的なのは戻り値の最適化です (下記 を参照)。C ++ 標準で説明されているもう 1 つの広く実装されている最適化は、クラス型一時オブジェクトが同じ型のオブジェクトにコピーされる場合です。 [ 1 ] [ 2 ]結果として、コピー初期化は通常、パフォーマンスの点では直接初期化と同等ですが、意味的には同等ではありません。コピー初期化では、アクセス可能なコピー コンストラクターが依然として必要です。[ 3 ]最適化は、参照にバインドされている一時オブジェクトには適用できません。

stdをインポートします// グローバルな可変変数int n = 0 ;struct MyStruct { explicit MyStruct ([[ maybe_unused ]] int x ) {} MyStruct ([[ maybe_unused ]] const MyStruct & x ) { ++ n ; // コピー コンストラクターには目に見える副作用があります// 静的ストレージ期間を持つオブジェクトを変更します} };int main () { MyStruct c1 ( 42 ); // 直接初期化、MyStruct::MyStruct(int) を呼び出すMyStruct c2 = MyStruct ( 42 ); // コピー初期化、MyStruct::MyStruct(const MyStruct&) を呼び出すstd :: println ( "{}" , n ); // コピーが省略された場合は0を、そうでない場合は1を出力します}

標準規格によれば、同様の最適化はスローおよびキャッチされるオブジェクトにも適用できる可能性があるが[ 4 ] [ 5 ]、最適化がスローされたオブジェクトから例外オブジェクトへのコピーと、例外オブジェクトからcatch節例外宣言で宣言されたオブジェクトへのコピーの両方に適用されるかどうかは不明である。また、この最適化が一時オブジェクトのみに適用されるのか、それとも名前付きオブジェクトにも適用されるのかについても不明である。[ 6 ]以下のソースコードがあるとする。

stdをインポートします構造体MyStruct { MyStruct () = default ; MyStruct ([[ maybe_unused ]] const MyStruct & x ) { std :: println ( "Hello World!" ); } };void f () { MyStruct c ; throw c ; // 名前付きオブジェクト c を例外オブジェクトにコピーします。// このコピーが省略できるかどうかは不明です。}int main () { try { f (); } catch ( MyStruct c ) { // 例外宣言で例外オブジェクトを一時的にコピーします// このコピーが省略できるかどうかも不明です。} }

したがって、準拠コンパイラは「Hello World!」を2回出力するプログラムを生成する必要があります。C++標準のC++11リビジョンでは、これらの問題が解決され、基本的に名前付きオブジェクトから例外オブジェクトへのコピーと、例外ハンドラで宣言されたオブジェクトへのコピーの両方を省略できるようになりました。[ 6 ]

GCCにはコピー省略を無効にするオプションがあります-fno-elide-constructors。このオプションは、戻り値の最適化やコピー省略を伴うその他の最適化の効果を観察(または観察しない)するのに便利です。この重要な最適化を無効にすることは、一般的に推奨されません。

C++17では「コピー省略の保証」が提供されており、prvalueは必要になるまで実体化されず、最終的な宛先のストレージに直接構築されます。[ 7 ]

戻り値の最適化

C++プログラミング言語において、戻り値最適化RVO)は、関数の戻り値を保持するために作成された一時オブジェクトを削除するコンパイラ最適化である。 [ 8 ] RVOは、C++標準によって、結果として得られるプログラムの観測可能な動作を変更することが許可されている。[ 9 ]

まとめ

一般的に、C++標準では、結果として得られる実行ファイルが、標準のすべての要件が満たされているかのように(つまり、そのように装っているかのように)同じ動作を示す限り、コンパイラが任意の最適化を実行することが許可されています。これは一般に「 as-ifルール」と呼ばれています。[ 10 ] [ 2 ]戻り値最適化という用語は、C++標準の特別な条項を指し、「as-if」ルールよりもさらに踏み込んだものです。実装は、コピーコンストラクタに副作用があっても、return文から生じるコピー操作を省略することができます。[ 1 ] [ 2 ]

以下の例は、コピーコンストラクタに目に見える副作用(テキストの出力)がある場合でも、実装によって作成されるコピーの一方または両方が削除される可能性があるシナリオを示しています。[ 1 ] [ 2 ]削除される可能性がある最初のコピーは、関数の戻り値MyStructにコピーされる可能性のある名前のない一時オブジェクトです。削除される可能性がある2番目のコピーは、 toによって返される一時オブジェクトのコピーです。 ffobj

stdをインポートしますstruct MyStruct { MyStruct () = default ; MyStruct ([[ maybe_unused ]] const MyStruct & x ) { std :: println ( "コピーが作成されました。" ); } };MyStruct f () { MyStruct ()を返します; }int main () { std :: println ( "Hello World!" ); MyStruct obj = f (); }

コンパイラとそのコンパイラの設定に応じて、結果のプログラムは次のいずれかの出力を表示する場合があります。

「こんにちは世界」 コピーが作成されました。 コピーが作成されました。
「こんにちは世界」 コピーが作成されました。
「こんにちは世界」

背景

関数から組み込み型のオブジェクトを返す場合、そのオブジェクトは通常CPUレジスタに収まるため、オーバーヘッドはほとんど、あるいは全く発生しません。クラス型のより大きなオブジェクトを返す場合、メモリ上の位置から別の位置へのコピーにコストがかかる可能性があります。これを回避するため、実装では呼び出し元のスタックフレームに隠しオブジェクトを作成し、そのオブジェクトのアドレスを関数に渡すことができます。関数の戻り値は、この隠しオブジェクトにコピーされます。[ 11 ]したがって、次のようなコードでは、

構造体Data { charバイト[ 16 ]; };Data f () { Data result ; // 結果を生成するreturn result ; }int main () {データd = f (); }

次のようなコードを生成する可能性があります:

構造体Data { charバイト[ 16 ]; };Data * f ( Data * _hiddenAddress ) { Data result ; // 結果を隠しオブジェクトにコピー* _hiddenAddress = result ; return _hiddenAddress ; }int main () { Data _hidden ; // 隠しオブジェクトを作成Data d = * f ( & _hidden ); // 結果を d にコピー}

これにより、Dataオブジェクトが 2 回コピーされます。

C++の進化の初期段階では、関数からクラス型のオブジェクトを効率的に返すことができないという言語の弱点が問題視されていました。 [ 12 ] 1991年頃、ウォルター・ブライトはコピーを最小限に抑える手法を実装し、関数内の隠しオブジェクトと名前付きオブジェクトを、結果を保持するために使用されるオブジェクトに効果的に置き換えました。[ 13 ]

構造体Data { charバイト[ 16 ]; };void f ( Data * p ) { // *p に直接結果を生成する}int main () {データd ; f ( & d ); }

ブライトはこの最適化をZortech C++コンパイラに実装した。[ 12 ]この手法は後に「名前付き戻り値最適化」(NRVO)と名付けられ、名前付きオブジェクトのコピーが省略されるという事実に由来している。[ 13 ]

コンパイラサポート

戻り値の最適化はほとんどのコンパイラでサポートされています。[ 8 ] [ 14 ] [ 15 ] ただし、コンパイラが最適化を実行できない状況もあります。よくあるケースの1つは、関数が実行パスに応じて異なる名前付きオブジェクトを返す場合です。[ 11 ] [ 14 ] [ 16 ]

stdをインポートしますstd :: stringを使用しますstring f ( bool cond = false ) { string first = "first" ; string second = "second" ; // 関数は引数に応じて2つの名前付きオブジェクトのいずれかを返す場合があります。RVOは適用されない可能性があります。return cond ? first : second ; }int main () {文字列結果= f (); }

参考文献

  1. ^ a b c ISO / IEC (2003). ISO/IEC 14882:2003(E): プログラミング言語 - C++ §12.8 クラスオブジェクトのコピー [class.copy]パラグラフ15
  2. ^ a b c d ISO / IEC (2003). 「§ 12.8 クラスオブジェクトのコピー [class.copy]」. ISO/IEC 14882:2003(E): プログラミング言語 - C++ (PDF) . パラグラフ 15. 2023年4月10日時点のオリジナル(PDF)からのアーカイブ。 2024年2月26日閲覧
  3. ^ Sutter, Herb (2001). More Exceptional C++ . Addison-Wesley.
  4. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): プログラミング言語 - C++ §15.1 例外のスロー [except.throw]パラグラフ5
  5. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): プログラミング言語 - C++ §15.3 例外処理 [except.handle]パラグラフ17
  6. ^ a b「C++標準コア言語の欠陥レポート」 . WG21 . 2009年3月27日閲覧
  7. ^ 「コピー省略 - cppreference.com」 . en.cppreference.com .
  8. ^ a bマイヤーズ、スコット (1995). More Effective C++ . Addison-Wesley. ISBN 9780201633719
  9. ^アレクサンドルスク、アンドレイ (2003-02-01)。「コンストラクターの移動」ドブ博士の日記2009 年 3 月 25 日に取得
  10. ^ ISO / IEC (2003). ISO/IEC 14882:2003(E): プログラミング言語 - C++ §1.9 プログラム実行 [intro.execution]パラグラフ1
  11. ^ a b Bulka, Dov; David Mayhew (2000). Efficient C++ . Addison-Wesley. ISBN 0-201-37950-3
  12. ^ a b Lippman, Stan (2004-02-03). 「名前の戻り値の最適化」 . Microsoft . 2009年3月23日閲覧。
  13. ^ a b「Glossary D Programming Language 2.0」 Digital Mars . 2009年3月23日閲覧
  14. ^ a b Shoukry, Ayman B. (2005年10月). 「Visual C++ 2005における名前付き戻り値の最適化」 . Microsoft . 2009年3月20日閲覧。
  15. ^ 「C++方言を制御するオプション」 . GCC . 2001年3月17日. 2018年1月20日閲覧
  16. ^ Hinnant, Howard; et al. (2002-09-10). 「N1377: C++言語にムーブセマンティクスサポートを追加するための提案」 WG21 . 2009年3月25日閲覧