名前空間
変種
操作

コピー省略

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

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

  • 定数式および定数初期化では、すべてのコピー省略が保証されます (ノート: これは 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 は b を指します (NRVO が保証されます)。
 
void g() {
    A c = g();       // c.p は c を指すかもしれないし、短命な一時オブジェクトを指すかもしれません。
}
(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

[編集] 関連項目