名前空間
変種
操作

コピーの省略

From cppreference.com
< cpp‎ | language
 
 
C++言語
全般
フロー制御
条件実行文
if
繰り返し文 (ループ)
for
範囲for (C++11)
ジャンプ文
関数
関数宣言
ラムダ式
inline指定子
動的例外仕様 (C++17まで*)
noexcept指定子 (C++11)
例外
名前空間
指定子
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点数
文字 - 文字列 - nullptr (C++11)
ユーザー定義 (C++11)
ユーティリティ
属性 (C++11)
typedef宣言
型エイリアス宣言 (C++11)
キャスト
メモリ確保
クラス
クラス固有の関数プロパティ
explicit (C++11)
static

特殊メンバ関数
テンプレート
その他
 
 

特定の基準が満たされる場合、クラスオブジェクトが同じ型のソースオブジェクト(cv修飾を無視)から作成されるのを省略できます。これは、オブジェクトに対して選択されたコンストラクタやデストラクタが副作用を持つ場合でも同様です。このオブジェクト作成の省略はコピー省略 (copy elision) と呼ばれます。

目次

[編集] 説明

コピー省略は、以下の状況で許可されます(これらは複数のコピーを削除するために組み合わせることができます)。

  • クラスの戻り値型を持つ関数のreturnステートメントで、オペランドが自動記憶域期間を持つ非volatileオブジェクトobjの名前である場合(関数パラメータまたはハンドラパラメータを除く)、結果オブジェクトのコピー初期化は、objを関数の呼び出しの結果オブジェクトに直接構築することで省略できます。このコピー省略のバリアントは、名前付き戻り値最適化 (named return value optimization) (NRVO) として知られています。
  • クラスオブジェクトtargetが、参照にバインドされていない一時的なクラスオブジェクトobjでコピー初期化される場合、コピー初期化は、objtargetに直接構築することで省略できます。このコピー省略のバリアントは、名前なし戻り値最適化 (unnamed return value optimization) (URVO) として知られています。C++17以降、URVOは必須となり、もはやコピー省略の一形態とは見なされません。詳細については以下を参照してください。
(C++17まで)
  • throwで、オペランドが、最も内側のtryブロック(存在する場合)を含まないスコープに属する自動記憶域期間を持つ非volatileオブジェクトobjの名前である場合(関数パラメータまたはハンドラパラメータを除く)、例外オブジェクトのコピー初期化は、objを例外オブジェクトに直接構築することで省略できます。
  • ハンドラで、ハンドラ引数のコンストラクタおよびデストラクタの実行を除いてプログラムの意味が変わらない場合、ハンドラパラメータを例外オブジェクトの別名として扱うことで、ハンドラ引数のコピー初期化を省略できます。
(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オブジェクトは破棄されないにもかかわらず、戻り値の型のデストラクタは、returnステートメントの時点でアクセス可能で、削除されていない必要があります。
  • オブジェクトの初期化で、初期化子式が変数型と同じクラス型(cv修飾を無視)のprvalueである場合。
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 コピー(/ムーブ)操作のみが省略可能であったが、
非コピー(/ムーブ)コンストラクタはコピー初期化によって選択されうる
任意のオブジェクト構築を省略する
関連するコピー初期化の

[編集] 関連項目

English 日本語 中文(简体) 中文(繁體)