コピー省略
コピーおよびムーブ (C++11以上)コンストラクタを省略し、ゼロコピーの値渡しセマンティクスを実現します。
目次 |
[編集] 説明
|
以下の状況において、コンパイラは、たとえコピー/ムーブコンストラクタおよびデストラクタが観察可能な副作用を持っていたとしても、クラスオブジェクトのコピーおよびムーブ構築を省略することが要求されます。 オブジェクトは、それらのコピー/ムーブ先の記憶域に、直接構築されます。 コピー/ムーブコンストラクタは存在するまたはアクセス可能である必要はありません。 言語のルールにより、概念的にさえも、コピー/ムーブ操作が行われないことが保証されます。 T f() { return T(); } f(); // T のデフォルトコンストラクタが一度だけ呼ばれます。 T x = T(T(f())); // x を初期化するために T のデフォルトコンストラクタが一度だけ呼ばれます。 ノート: 上記のルールは最適化を規定しているのではありません。 C++17 コア言語の prvalue および一時オブジェクトの仕様は C++ の以前の版のそれとは根本的に異なります。 コピー/ムーブ元の一時オブジェクトはもはや存在しません。 C++17 の仕組みを説明する別の方法は「具体化されない値渡し」です。 prvalue は一時オブジェクトを一度も具体化することなく返され、使用されます。 |
(C++17以上) |
以下の状況において、コンパイラは、たとえコピー/ムーブ (C++11以上)コンストラクタおよびデストラクタが観察可能な副作用を持っていたとしても、クラスオブジェクトのコピーおよびムーブ (C++11以上)構築を省略することが許されますが、要求はされません。 オブジェクトは、それらのコピー/ムーブ先の記憶域に、直接構築されます。 これは最適化です。 コピー/ムーブ (C++11以上)コンストラクタは、たとえ呼ばれないときでも、 (最適化がまったく行われなかった場合のように) 存在し、アクセス可能でなければなりません。 そうでなければ、プログラムは ill-formed です。
- return 文において、被演算子が自動記憶域期間を持つ非 volatile オブジェクトの名前であり、それが関数の引数または catch 節の引数でなく、関数の戻り値の型と同じクラス型 (cv 修飾は無視します) であるとき。 コピー省略のこの変種は NRVO (named return value optimization) と呼ばれます。
|
(C++17未満) |
|
戻り値の最適化は必須であり、もはやコピー省略とみなされません。 上を参照してください。 |
(C++17以上) |
|
(C++11以上) |
|
(C++20以上) |
コピー省略が発生したとき、処理系は省略されたコピー/ムーブ (C++11以上)操作のソースとターゲットを単に同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は最適化がなかった場合に2つのオブジェクトが破棄されたであろうときに発生します (ただし、選択されたコンストラクタの引数がオブジェクト型への右辺値参照の場合は、破棄はターゲットが破棄されたであろうときに発生します) (C++17以上)。
複数のコピーを省略するために複数のコピー省略が連鎖することがあります。
struct A { void *p; constexpr A(): p(this) {} }; constexpr A g() { A a; return a; } constexpr A a; // a.p は a を指します。 constexpr A b = g(); // b.p は一時オブジェクトを指すため、ダングリングになります。 // (N4810 の文言は矛盾しています) void g() { A c = g(); // c.p は c を指すかもしれないし、短命な一時オブジェクトを指すかもしれません。 } extern const A d; constexpr A f() { A e; if (&e == &d) return A(); else return e; // 定数評価文脈において NRVO を必須にすると、 // NRVO が行われない場合に限り NRVO が行われるという矛盾を発生させるでしょう。 } constexpr A d = f(); // d.p はダングリングです。 |
(C++14以上) |
[編集] ノート
コピー省略は、観察可能な副作用を変えることができる、唯一許されている形式の最適化 (C++14未満)確保の省略および拡張と並んで許されている2つの形式の最適化のひとつ (C++14以上)です。 コンパイラによっては許されているすべての場面でコピー省略を行うとは限らないため (デバッグモードの場合など)、コピー/ムーブコンストラクタおよびデストラクタの副作用に依存するプログラムは移植性がありません。
|
return 文または throw 式において、コピー省略を行うことはできないけれどもコピー省略の条件を満たすまたは満たすであろう場合 (ソースが関数の引数の場合を除く)、コンパイラは、たとえそのオブジェクトが左辺値によって指定されていても、ムーブコンストラクタの使用を試みます。 詳細については return 文を参照してください。 |
(C++11以上) |
[編集] 例
#include <iostream> #include <vector> struct Noisy { Noisy() { std::cout << "constructed\n"; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed\n"; } }; std::vector<Noisy> f() { std::vector<Noisy> v = std::vector<Noisy>(3); // copy elision when initializing v // from a temporary (until C++17) // from a prvalue (since C++17) return v; // NRVO from v to the result object (not guaranteed, even in C++17) } // if optimization is disabled, the move constructor is called void g(std::vector<Noisy> arg) { std::cout << "arg.size() = " << arg.size() << '\n'; } int main() { std::vector<Noisy> v = f(); // copy elision in initialization of v // from the temporary returned by f() (until C++17) // from the prvalue f() (since C++17) g(f()); // copy elision in initialization of the parameter of g() // from the temporary returned by f() (until C++17) // from the prvalue f() (since C++17) }
出力例:
constructed constructed constructed constructed constructed constructed arg.size() = 3 destructed destructed destructed destructed destructed destructed
[編集] 欠陥報告
以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。
| DR | 適用先 | 発行時の動作 | 正しい動作 |
|---|---|---|---|
| CWG 2022 | C++14 | copy elision was optional in constant expressions | copy elision mandatory |
| CWG 2278 | C++14 | NRVO was mandatory in constant expressions | forbid NRVO in constant expressions |