コピーの省略
特定の基準が満たされる場合、クラスオブジェクトが同じ型のソースオブジェクト(cv修飾を無視)から作成されるのを省略できます。これは、オブジェクトに対して選択されたコンストラクタやデストラクタが副作用を持つ場合でも同様です。このオブジェクト作成の省略はコピー省略 (copy elision) と呼ばれます。
目次 |
[編集] 説明
コピー省略は、以下の状況で許可されます(これらは複数のコピーを削除するために組み合わせることができます)。
- クラスの戻り値型を持つ関数のreturnステートメントで、オペランドが自動記憶域期間を持つ非volatileオブジェクトobjの名前である場合(関数パラメータまたはハンドラパラメータを除く)、結果オブジェクトのコピー初期化は、objを関数の呼び出しの結果オブジェクトに直接構築することで省略できます。このコピー省略のバリアントは、名前付き戻り値最適化 (named return value optimization) (NRVO) として知られています。
|
(C++17まで) |
| (C++11以降) | |
|
(C++20以降) |
コピー省略が発生する場合、実装は省略された初期化のソースとターゲットを、単に同じオブジェクトを参照する2つの異なる方法として扱います。
|
破棄は、最適化なしで2つのオブジェクトが破棄されたはずの時間の遅い方で発生します。 |
(C++11まで) |
|
選択されたコンストラクタの最初のパラメータがオブジェクトの型への右辺値参照である場合、そのオブジェクトの破棄はターゲットが破棄されたはずのときに発生します。それ以外の場合、破棄は、最適化なしで2つのオブジェクトが破棄されたはずの時間の遅い方で発生します。 |
(C++11以降) |
prvalueのセマンティクス(「保証されたコピー省略」)C++17以降、prvalueは必要になるまで具現化されず、最終的な目的地の記憶域に直接構築されます。これは、言語構文が視覚的にコピー/ムーブを示唆する場合(例:コピー初期化)でも、コピー/ムーブが実行されないことを意味します。つまり、型にアクセス可能なコピー/ムーブコンストラクタが全く不要になります。例としては以下が挙げられます。
T f() { return U(); // constructs a temporary of type U, // then initializes the returned T from the temporary } T g() { return T(); // constructs the returned T directly; no move }
T x = T(T(f())); // x is initialized by the result of f() directly; no move
struct C { /* ... */ }; C f(); struct D; D g(); struct D : C { D() : C(f()) {} // no elision when initializing a base class subobject D(int) : D(g()) {} // no elision because the D object being initialized might // be a base-class subobject of some other class }; 注:このルールは最適化を規定するものではなく、標準はこれを正式に「コピー省略」(何も省略されていないため)とは記述していません。その代わり、C++17のコア言語仕様におけるprvalueと一時オブジェクトは、以前のC++リビジョンとは根本的に異なります。コピー/ムーブ元となる一時オブジェクトはもはや存在しません。C++17のメカニズムを説明する別の方法は、「具現化されない値渡し」または「一時オブジェクトの具現化の遅延」です。prvalueは、一時オブジェクトを具現化することなく返され、使用されます。 |
(C++17以降) |
[編集] 注釈
コピー省略は、許容される唯一の最適化形式でした(C++14まで) アロケーション省略と拡張と並んで、許容される2つの最適化形式の1つです(C++14以降)。これは観測可能な副作用を変更できます。一部のコンパイラは、許可されているすべての状況でコピー省略を実行しないため(例:デバッグモード)、コピー/ムーブコンストラクタとデストラクタの副作用に依存するプログラムは移植性がありません。
|
returnステートメントまたはthrow式で、コンパイラがコピー省略を実行できないが、コピー省略の条件が満たされている場合、またはソースが関数パラメータである場合を除いて満たされるはずの場合、ソースオペランドがlvalueで指定されている場合でも、コンパイラはムーブコンストラクタの使用を試みます(C++23まで) ソースオペランドはrvalueとして扱われます(C++23以降)。詳細については、returnステートメントを参照してください。 定数式および定数初期化では、コピー省略は決して実行されません。 struct A { void* p; constexpr A() : p(this) {} A(const A&); // Disable trivial copyability }; constexpr A a; // OK: a.p points to a constexpr A f() { A x; return x; } constexpr A b = f(); // error: b.p would be dangling and point to the x inside f constexpr A c = A(); // (until C++17) error: c.p would be dangling and point to a temporary // (since C++17) OK: c.p points to c; no temporary is involved |
(C++11以降) |
| 機能テストマクロ | 値 | 規格 | 機能 |
|---|---|---|---|
__cpp_guaranteed_copy_elision |
201606L |
(C++17) | 簡略化された値カテゴリによる保証されたコピー省略 |
[編集] 例
#include <iostream> struct Noisy { Noisy() { std::cout << "constructed at " << this << '\n'; } Noisy(const Noisy&) { std::cout << "copy-constructed\n"; } Noisy(Noisy&&) { std::cout << "move-constructed\n"; } ~Noisy() { std::cout << "destructed at " << this << '\n'; } }; Noisy f() { Noisy v = Noisy(); // (until C++17) copy elision initializing v from a temporary; // the move constructor may be called // (since C++17) "guaranteed copy elision" return v; // copy elision ("NRVO") from v to the result object; // the move constructor may be called } void g(Noisy arg) { std::cout << "&arg = " << &arg << '\n'; } int main() { Noisy v = f(); // (until C++17) copy elision initializing v from the result of f() // (since C++17) "guaranteed copy elision" std::cout << "&v = " << &v << '\n'; g(f()); // (until C++17) copy elision initializing arg from the result of f() // (since C++17) "guaranteed copy elision" }
実行結果の例
constructed at 0x7fffd635fd4e &v = 0x7fffd635fd4e constructed at 0x7fffd635fd4f &arg = 0x7fffd635fd4f destructed at 0x7fffd635fd4f destructed at 0x7fffd635fd4e
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 1967 | C++11 | コピー省略がムーブコンストラクタを使用して行われる場合、 ムーブ元のオブジェクトの寿命はまだ考慮されていた |
考慮されなかった |
| CWG 2426 | C++17 | prvalueを返すときにデストラクタは不要だった | デストラクタは潜在的に呼び出される |
| CWG 2930 | C++98 | コピー(/ムーブ)操作のみが省略可能であったが、 非コピー(/ムーブ)コンストラクタはコピー初期化によって選択されうる |
任意のオブジェクト構築を省略する 関連するコピー初期化の |