1. ホーム
  2. c++

[解決済み】コピー初期化と直接初期化は違うのですか?

2022-04-02 11:43:30

質問

このような関数があるとします。

void my_test()
{
    A a1 = A_factory_func();
    A a2(A_factory_func());

    double b1 = 0.5;
    double b2(0.5);

    A c1;
    A c2 = A();
    A c3(A());
}

各グループ分けにおいて、これらの文は同一ですか?それとも、いくつかの初期化で余分な(おそらく最適化可能な)コピーがあるのでしょうか?

両方のことを言っている人を見たことがあります。お願い 引用 のテキストを証拠として提出します。また、他の事例も追加してください。

解決方法は?

C++17のアップデート

C++17では A_factory_func() は、一時的なオブジェクトの作成(C++<=14)から、C++17 では、この式が初期化される任意のオブジェクトの初期化を(大まかに言えば)指定するだけに変更されました。これらのオブジェクト("結果オブジェクト"と呼ばれる)は、宣言によって作成される変数(例えば a1 のように)、初期化が破棄されたときに作成される人工的なオブジェクト、参照結合のためにオブジェクトが必要な場合(例えば A_factory_func(); . 最後のケースでは、オブジェクトが人工的に作成され、quot;一時的な実体化"と呼ばれます。 A_factory_func() は変数や参照を持たないので、そうでなければオブジェクトが存在する必要があります)。

私たちの場合の例として a1a2 と同じ型のprvalueイニシャライザの結果オブジェクトは、このような宣言では、特別なルールがあります。 a1 は変数 a1 であり、したがって A_factory_func() はオブジェクトを直接初期化します。 a1 . 中間的な関数型キャストは何の効果も持ちません。 A_factory_func(another-prvalue) は、外側のprvalueの結果オブジェクトを通過して、内側のprvalueの結果オブジェクトになるだけです。


A a1 = A_factory_func();
A a2(A_factory_func());

どのようなタイプかによる A_factory_func() を返します。私は、それが A - ただし、コピーコンストラクタが明示的である場合は、1回目のコピーコンストラクタが失敗します。読む 8.6/14

double b1 = 0.5;
double b2(0.5);

これは組み込み型(ここではクラス型ではないという意味)だから同じことをしているのです。読む 8.6/14 .

A c1;
A c2 = A();
A c3(A());

これは同じことをやっているわけではありません。最初のデフォルト初期化では、もし A が非PODの場合は初期化を行わず、PODの場合は初期化を行いません(Read 8.6/9 ). 2回目のコピーで初期化します。値を一時的に初期化し、その値を c2 (読み 5.2.3/2 8.6/14 ). もちろん、これには明示的でないコピーコンストラクタが必要になります (Read 8.6/14 12.3.1/3 13.3.1.3/1 ). 3番目は関数宣言を作成し c3 を返します。 A を返す関数への関数ポインタを取る。 A (読み 8.2 ).


初期化について ダイレクトとコピーの初期化

この2つの形式は同じように見えて、同じことをするはずなのですが、ある場合には驚くほど異なっています。初期化には、直接初期化とコピー初期化という2つの形式があります。

T t(x);
T t = x;

それぞれに帰属させることができる動作があります。

  • 直接初期化は、オーバーロードされた関数への関数呼び出しのように動作します。この場合、関数は T (を含む)。 explicit のもの)で、引数は x . オーバーロードの解決は、最適なコンストラクタを見つけ、必要な場合は暗黙の変換を行います。
  • コピー初期化では、暗黙の変換シーケンスを構築します。を変換しようとします。 x 型のオブジェクトに変換します。 T . (その後、そのオブジェクトを初期化対象のオブジェクトにコピーする可能性があるので、コピーコンストラクタも必要です - しかし、これは以下では重要ではありません)

ご覧の通りです。 コピー初期化 は、暗黙の変換の可能性に関して、ある意味で直接の初期化の一部であると言えます。直接の初期化ではすべてのコンストラクタが呼び出し可能であるのに対して さらに は引数の型を一致させるために必要な暗黙の変換を行うことができ、コピー初期化は1つの暗黙の変換シーケンスをセットアップするだけでよいのです。

頑張ってみたら は、それらのフォームごとに異なるテキストを出力するために次のようなコードを得ました。 を使用せずに、quot;overvious" を通して、quot;overvious" を使用します。 explicit のコンストラクタを使用します。

#include <iostream>
struct B;
struct A { 
  operator B();
};

struct B { 
  B() { }
  B(A const&) { std::cout << "<direct> "; }
};

A::operator B() { std::cout << "<copy> "; return B(); }

int main() { 
  A a;
  B b1(a);  // 1)
  B b2 = a; // 2)
}
// output: <direct> <copy>

どのような仕組みで、なぜそのような結果が出力されるのでしょうか?

  1. 直接初期化

    まず、変換について何も知らない。ただ、コンストラクタを呼ぼうとします。この場合、次のコンストラクタが利用可能で、これは 完全一致 :

    B(A const&)
    
    

    そのコンストラクタを呼び出すために必要な変換、ましてやユーザ定義の変換はありません(ここでも const qualification の変換は行われないことに注意してください)。そして、直接の初期化でそれを呼び出すことになります。

  2. コピー初期化

    上記のように,コピー初期化は a が型でない場合 B またはそれに由来するものです(ここでは明らかにそうです)。そこで、変換を行う方法を探し、次のような候補を見つけることになる。

    B(A const&)
    operator B(A&);
    
    

    変換関数をどのように書き換えたかに注目してください。パラメータの型は this のポインタがあり、非定数のメンバ関数では、非定数になります。さて、これらの候補を x を引数として与えます。勝者は変換関数である。なぜなら、もし2つの候補関数が両方とも同じ型への参照を受け取るならば less const のバージョンが勝ります(ちなみにこれは、非定数のオブジェクトに対して非定数のメンバ関数呼び出しを優先するメカニズムでもあります)。

    変換関数をconstメンバ関数に変更すると、変換が曖昧になることに注意してください(どちらもパラメータの型が A const& では)。Comeauコンパイラはそれを適切に拒絶しますが、GCCは非ペダンティックモードでそれを受け入れます。に切り替えます。 -pedantic でも、ちゃんと曖昧さの警告も出力されます。

この2つの形式がどのように違うのか、多少なりともご理解いただけると幸いです。