21

この構文が正当であるためには、クラスに有効なコピーまたは移動コンストラクターが必要です。

C x = factory();
C y( factory() );
C z{ factory() };

C++03 では、コンパイラがコピー コンストラクターに触れるのを防ぐために、コピー省略に頼ることがかなり一般的でした。定義が存在するかどうかに関係なく、すべてのクラスには有効なコピー コンストラクターシグネチャがあります。

C++11 では、コピー不可能な型は を定義する必要C( C const & ) = delete;があり、関数への参照は使用に関係なく無効になります (移動不可能な場合も同様)。(C++11 §8.4.3/2)。たとえば、GCC は、そのようなオブジェクトを値で返そうとすると文句を言います。コピーの省略は役に立ちません。

幸いなことに、抜け穴に頼るのではなく、意図を表現するための新しい構文もあります。このfactory関数は波括弧初期化リストを返して、結果の一時的なインプレースを構築できます。

C factory() {
    return { arg1, 2, "arg3" }; // calls C::C( whatever ), no copy
}

編集:疑問がある場合、このreturnステートメントは次のように解析されます。

  1. 6.6.3/2: 「ブレース初期化リストを含む return ステートメントは、指定された初期化子リストからのコピー リスト初期化 (8.5.4) によって、関数から返されるオブジェクトまたは参照を初期化します。」
  2. 8.5.4/1: 「コピー初期化コンテキストでのリスト初期化は、コピーリスト初期化と呼ばれます。」¶3: 「T がクラス型の場合、コンストラクターが考慮されます。適用可能なコンストラクターが列挙され、オーバーロードの解決を通じて最適なコンストラクターが選択されます (13.3、13.3.1.7)。」

copy-list-initializationという名前に惑わされないでください。8.5:

13: 初期化の形式 (かっこまたは を使用=) は一般に重要ではありませんが、初期化子または初期化されるエンティティがクラス型を持つ場合は問題になります。下記参照。初期化されるエンティティにクラス型がない場合、括弧で囲まれた初期化子の式リストは単一の式になります。

14: T x = a; 引数の受け渡し、関数の戻り、例外のスロー (15.1)、例外の処理 (15.3)、および集合メンバーの初期化 (8.5.1) と同様にフォームで発生する初期化は、コピー初期化と呼ばれます。

copy-initialization とその代替であるdirect-initializationの両方は、初期化子が波括弧初期化リストである場合、常に list-initialization に従います。を追加しても意味的な効果はありません=。これが、リスト初期化が非公式に均一初期化と呼ばれる理由の 1 つです。

違いがあります。直接初期化は、コピー初期化とは異なり、明示的なコンストラクターを呼び出す場合があります。コピー初期化は、変換時に一時オブジェクトを初期化し、それをコピーしてオブジェクトを初期化します。

ステートメントのcopy-list-initializationreturn { list }指定は、まったく同等の構文を betemp T = { list };に指定するだけです。ここで、=copy-initialization を示します。コピー コンストラクターが呼び出されることをすぐに意味するわけではありません。

-- 編集を終了します。


次に、関数の結果を右辺値参照に受け取って、一時的なものをローカルにコピーしないようにすることができます。

C && x = factory(); // applies to other initialization syntax

問題は、コピー不可、移動不可の型を返すファクトリ関数から非静的メンバーを初期化する方法です。参照メンバーは一時の有効期間を延長しないため、参照のトリックは機能しません。

集約初期化は考慮していないことに注意してください。これは、コンストラクターの定義に関するものです。

4

2 に答える 2

2

あなたの主な質問について:

問題は、コピー不可能で移動不可能な型を返すファクトリ関数から非静的メンバーを初期化する方法です。

あなたはそうしない。

問題は、戻り値がどのように生成されるかと、呼び出しサイトで戻り値がどのように使用されるかという2つのことを混同しようとしていることです。これらの2つのものは互いに接続していません。注意:関数の定義は、コンパイラーが必ずしも使用できるとは限らないため、(言語の観点から)その使用方法に影響を与えることはできません。したがって、C ++では、戻り値が生成された方法が何かに影響を与えることはできません(言語要件ではなく、最適化である省略以外)。

別の言い方をすれば、これは:

C c = {...};

これとは異なります:

C c = [&]() -> C {return {...};}()

タイプを値で返す関数があります。タイプのprvalue式を返していCます。この値を保存して名前を付ける場合は、次の2つのオプションがあります。

  1. const&またはとして保存し&&ます。これにより、一時的なものの寿命が制御ブロックの寿命まで延長されます。メンバー変数ではそれを行うことはできません。関数内の自動変数でのみ実行できます。

  2. それをコピー/値に移動します。これはメンバー変数を使用して行うことができますが、タイプがコピー可能または移動可能である必要があることは明らかです。

これらは、prvalue式を格納する場合にC++が利用できる唯一のオプションです。したがって、型を移動可能にするか、新しく割り当てられたポインタをメモリに返し、値の代わりにそれを格納することができます。

この制限は、移動が最初に作成された理由の大きな部分です。つまり、価値によって物事を渡し、高価なコピーを回避できるようにするためです。言語を変更して、戻り値を強制的に削除することはできませんでした。その代わり、多くの場合、コストを削減しました。

于 2012-06-17T17:42:38.203 に答える