C ++ 17アップデート
C ++ 17では、A_factory_func()
一時オブジェクトの作成(C ++ <= 14)から、C ++ 17でこの式が(大まかに言えば)初期化されるオブジェクトの初期化を指定するだけに変更されました。これらのオブジェクト(「結果オブジェクト」と呼ばれます)は、宣言によって作成された変数(などa1
)、初期化が破棄されたときに作成された人工オブジェクト、またはオブジェクトが参照バインディングに必要な場合(など)ですA_factory_func();
。最後のケースでは、オブジェクトは、「一時的な実体化」と呼ばれる人工的に作成されます。これはA_factory_func()
、オブジェクトの存在を必要とする変数または参照がないためです)。
私たちの場合の例として、a1
およびのa2
特別なルールでは、そのような宣言では、と同じ型のprvalue初期化子の結果オブジェクトa1
がvariable 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
コピーコンストラクターが明示的である場合、最初のコンストラクターが失敗することを除いて、それは-を返しますが、同じことをしています- 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/1)。3番目は、c3
を返す関数の関数宣言を作成し、関数を返すA
関数への関数ポインターを受け取りますA
(Read 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
、適切なあいまいさの警告も出力されます。
これがこれらの2つの形式の違いを明確にするのに少し役立つことを願っています!
A c1; A c2 = c1; A c3(c1);
ます。