次のような関数があるとします:
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());
}
各グループで、これらのステートメントは同一ですか? または、一部の初期化に追加の (おそらく最適化可能な) コピーがありますか?
両方のことを言う人を見たことはあります。証拠としてテキストを引用してください。また、他の事例も追加してください。
ベストアンサー1
C++17 アップデート
C++17 では、 の意味は、A_factory_func()
一時オブジェクトの作成 (C++<=14) から、この式が初期化されるオブジェクトの初期化を (大まかに言えば) 指定するだけに変更されました。これらのオブジェクト (「結果オブジェクト」と呼ばれる) は、宣言によって作成される変数 ( などa1
)、初期化が破棄されたときに作成される人工オブジェクト、または参照バインディングにオブジェクトが必要な場合 ( の など) に作成される人工オブジェクトです。A_factory_func();
最後のケースでは、 には、オブジェクトの存在を必要とする変数または参照がないため、オブジェクトは人工的に作成され、「一時的実体化」と呼ばA_factory_func()
れます。
a1
私たちのケースの例として、およびの場合、a2
特別なルールでは、そのような宣言では、 と同じ型の prvalue 初期化子の結果オブジェクトはa1
変数 でありa1
、したがってA_factory_func()
オブジェクト を直接初期化すると規定されています。 は、外側の prvalue の結果オブジェクトを「通過」して、内側の prvalue の結果オブジェクトにもなるため、a1
中間の関数型キャストは効果がありません。A_factory_func(another-prvalue)
A a1 = A_factory_func();
A a2(A_factory_func());
返される型によって異なりますA_factory_func()
。- を返すと仮定するA
と、同じことを行いますが、コピー コンストラクタが明示的である場合は、最初のものは失敗します。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の場合は初期化を行いません(読み取り8.6/9)。2番目のコピーは、一時変数を値初期化し、その値をc2
(読み取り)にコピーします。5.2.3/2そして8.6/14)。もちろん、これには非明示的なコピーコンストラクタが必要になります(8.6/14そして12.3.1/3そして13.3.1.3/1c3
3番目は、関数を返しA
、関数ポインタを受け取る関数の関数宣言を作成しますA
(読み取り8.2)。
初期化の詳細直接初期化とコピー初期化
見た目は同じで、同じことを行うはずですが、これらの 2 つの形式は、特定のケースでは著しく異なります。初期化の 2 つの形式は、直接初期化とコピー初期化です。
T t(x);
T t = x;
それぞれに帰属できる動作があります:
- 直接初期化は、オーバーロードされた関数への関数呼び出しのように動作します。この場合、関数は のコンストラクター
T
( も含むexplicit
) であり、引数は ですx
。オーバーロード解決では、最も一致するコンストラクターが検索され、必要に応じて暗黙的な変換が行われます。 x
コピー初期化は暗黙的な変換シーケンスを構築します。つまり、 型のオブジェクトに変換しようとしますT
。(その後、そのオブジェクトを初期化先のオブジェクトにコピーする可能性があるため、コピー コンストラクターも必要ですが、これは以下では重要ではありません)
ご覧のとおり、コピー初期化は、暗黙的な変換の可能性に関して、ある意味では直接初期化の一部です。直接初期化では、すべてのコンストラクターを呼び出すことができ、さらに引数の型を一致させるために必要な暗黙的な変換を実行できますが、コピー初期化では、暗黙的な変換シーケンスを 1 つだけ設定できます。
私は一生懸命努力してそれぞれのフォームに異なるテキストを出力するための次のコードを取得しました、コンストラクターを介して「明白な」ものを使用せずに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>
それはどのように機能し、なぜその結果が出力されるのでしょうか?
直接初期化
最初は変換について何も知りません。コンストラクタを呼び出そうとするだけです。この場合、次のコンストラクタが使用可能であり、完全に一致します。
B(A const&)
そのコンストラクターを呼び出すために必要な変換はなく、ユーザー定義の変換はなおさらありません (ここでも const 修飾変換は行われないことに注意してください)。そのため、直接の初期化によってそれが呼び出されます。
コピーの初期化
a
上で述べたように、コピー初期化は、型がないか、または派生していない場合に(明らかにこの場合)、変換シーケンスを構築しますB
。したがって、変換を行う方法を探し、次の候補を見つけます。B(A const&) operator B(A&);
変換関数をどのように書き直したかに注目してください。パラメータ型はポインタの型を反映し
this
、非 const メンバー関数では非 const になります。ここで、これらの候補をx
引数として呼び出します。勝者は変換関数です。なぜなら、同じ型への参照を受け入れる 2 つの候補関数がある場合、const の少ないバージョンが勝つからです (ちなみに、これは非 const オブジェクトに対して非 const メンバー関数呼び出しを優先するメカニズムでもあります)。変換関数を const メンバー関数に変更すると、変換があいまいになることに注意してください (両方とも
A const&
then のパラメータ型を持つため)。Comeau コンパイラはこれを適切に拒否しますが、GCC は非ペダンティック モードでこれを受け入れます。ただし、 に切り替えると、-pedantic
適切なあいまいさの警告も出力されます。