名前空間
変種
操作

コピー省略

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

コピーおよびムーブ (C++11以上)コンストラクタを省略し、ゼロコピーの値渡しセマンティクスを実現します。

目次

[編集] 説明

以下の状況において、コンパイラは、たとえコピー/ムーブコンストラクタおよびデストラクタが観察可能な副作用を持っていたとしても、クラスオブジェクトのコピーおよびムーブ構築を省略することが要求されます。 オブジェクトは、それらのコピー/ムーブ先の記憶域に、直接構築されます。 コピー/ムーブコンストラクタは存在するまたはアクセス可能である必要はありません。 言語のルールにより、概念的にさえも、コピー/ムーブ操作が行われないことが保証されます。

  • return 文において、被演算子が関数の戻り値の型と同じクラス型 (cv 修飾は無視します) の prvalue のとき。
T f() {
    return T();
}
 
f(); // T のデフォルトコンストラクタが一度だけ呼ばれます。
  • 変数の初期化において、初期化子式が変数の型と同じクラス型 (cv 修飾は無視します) の prvalue のとき。
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; 名前付き戻り値の最適化) と言います。
  • オブジェクトの初期化において、ソースオブジェクトが名前のない一時オブジェクトであり、ターゲットオブジェクトと同じクラス型 (cv 修飾は無視します) のとき。 名前のない一時オブジェクトが return 文の被演算子のとき、コピー省略のこの変種は RVO (return value optimization; 戻り値の最適化) と言います。
(C++17未満)

戻り値の最適化は必須であり、もはやコピー省略とみなされません。 上を参照してください。

(C++17以上)
  • throw 式において、被演算子が自動記憶域期間を持つ非 volatile オブジェクトの名前であり、それが関数の引数または catch 節の引数でなく、そのスコープが最も内側の try ブロックを超えて延長されない (try ブロックがある場合) とき。
  • catch 節において、引数が投げられた例外と同じ型 (cv 修飾は無視します) のとき、例外オブジェクトのコピーは省略され、 catch 節の本体は、参照でキャッチしたかのように、その例外オブジェクトを直接アクセスします。 そのようなコピー省略が、 catch 節の引数のコピーコンストラクタおよびデストラクタをスキップする以外の何らかの理由でプログラムの観察可能な動作を変えるであろう場合は、これは無効化されます (例えば、 catch 節の引数が変更され、その例外オブジェクトが throw で投げ直される場合)。
(C++11以上)
  • コルーチンにおいて、引数のコルーチン状態へのコピー/ムーブは、それが引数のコンストラクタおよびデストラクタの呼び出しを省略すること以外にプログラムの動作を変更しなければ、省略されることがあります。 これは、引数が中断点より後に参照されることがない場合、またはコルーチン状態全体がそもそもヒープ確保されないときに、発生します。
(C++20以上)

コピー省略が発生したとき、処理系は省略されたコピー/ムーブ (C++11以上)操作のソースとターゲットを単に同じオブジェクトを参照する2つの異なる方法として扱い、そのオブジェクトの破棄は最適化がなかった場合に2つのオブジェクトが破棄されたであろうときに発生します (ただし、選択されたコンストラクタの引数がオブジェクト型への右辺値参照の場合は、破棄はターゲットが破棄されたであろうときに発生します) (C++17以上)

複数のコピーを省略するために複数のコピー省略が連鎖することがあります。

  • 定数式および定数初期化では、戻り値の最適化 (RVO) は保証されますが、名前付き戻り値の最適化 (NRVO) は禁止されます (ノート: これは C++14 の後の欠陥報告 CWG 2022 によって規定され、欠陥報告 CWG 2278 によって覆されました)。
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); // 一時オブジェクトから (C++17未満) または
                                                  // prvalue から (C++17以上)
                                                  // v を初期化するとき、コピー省略。
    return v; // v から戻り値オブジェクトへの NRVO (C++17でも保証はされていません)。
              // 最適化が無効な場合は、ムーブコンストラクタが呼ばれます。
 
void g(std::vector<Noisy> arg) {
    std::cout << "arg.size() = " << arg.size() << '\n';
}
 
int main() {
    std::vector<Noisy> v = f(); // f() から返された一時オブジェクトから (C++17未満) または
                                // f() の prvalue から (C++17以上) の
                                // v の初期化におけるコピー省略。
    g(f()); // f() から返された一時オブジェクトから (C++17未満) または
            // f() の prvalue から (C++17以上) の
            // g() の引数の初期化におけるコピー省略。
}

出力例:

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

[編集] 関連項目