直接初期化
提供: cppreference.com
明示的なコンストラクタ引数の集合からオブジェクトを初期化します。
構文
T object ( arg );
T object |
(1) | ||||||||
T object { arg };
|
(2) | (C++11以上) | |||||||
T ( other )
T |
(3) | ||||||||
static_cast< T >( other )
|
(4) | ||||||||
new T(args, ...)
|
(5) | ||||||||
Class::Class() : member(args, ...) { ... }
|
(6) | ||||||||
[arg](){ ... }
|
(7) | (C++11以上) | |||||||
説明
直接初期化は以下の状況で行われます。
1) 括弧で囲まれた空でない式または波括弧初期化子リスト (C++11以上)のリスト付きの初期化。
2) 波括弧で囲まれた単一の初期化子付きの非クラス型のオブジェクトの初期化 (ノート: クラス型や波括弧初期化子リストの他の使用方法については、リスト初期化を参照してください)。
5) 空でない初期化子付きの new 式による、動的記憶域期間を持つオブジェクトの初期化。
6) コンストラクタの初期化子リストによる、基底クラスまたは非静的データメンバの初期化。
7) ラムダ式における、コピーキャプチャされた変数からのクロージャオブジェクトのメンバの初期化。
直接初期化の効果は以下の通りです。
Tが配列型の場合、
|
(C++20未満) |
struct A { explicit A(int i = 0) {} };
A a[2](A(1)); // OK、 a[0] を A(1) で、 a[1] を A() で初期化します。
A b[2]{A(1)}; // エラー、 {} 選択された explicit コンストラクタからの a[1] の暗黙のコピーリスト初期化。
|
(C++20以上) |
Tがクラス型の場合、
| (C++17以上) |
Tのコンストラクタが調べられ、オーバーロード解決によってベストマッチが選択されます。 そしてそのコンストラクタがオブジェクトを初期化するために呼ばれます。
struct B {
int a;
int&& r;
};
int f();
int n = 10;
B b1{1, f()}; // OK、生存期間は延長されます。
B b2(1, f()); // well-formed ですが、ダングリング参照です。
B b3{1.0, 1}; // エラー、縮小変換。
B b4(1.0, 1); // well-formed ですが、ダングリング参照です。
B b5(1.0, std::move(n)); // OK。
|
(C++20以上) |
- そうでなく、
Tは非クラス型だけれどもソース型がクラス型の場合、そのソース型およびその基底クラスの変換関数 (もしあれば) が調べられ、オーバーロード解決によってベストマッチが選択されます。 そして選択されたユーザ定義変換が初期化子式を初期化中のオブジェクトに変換するために使用されます。 - そうでなく、
Tがboolであり、ソース型が std::nullptr_t の場合、初期化されたオブジェクトの値はfalseです。 - そうでなければ、 other の値を
Tの cv 修飾されていないバージョンに変換するために標準変換が (必要であれば) 使用され、初期化中のオブジェクトの初期値はその (必要に応じて変換された) 値になります。
ノート
直接初期化はコピー初期化よりも寛容です。 コピー初期化は非 explicit コンストラクタと非 explicit ユーザ定義変換関数しか考慮しないのに対して、直接初期化はすべてのコンストラクタとすべてのユーザ定義変換関数を考慮します。
直接初期化の構文 (1) (丸括弧付き) を用いた変数宣言と関数宣言との間で曖昧な場合、コンパイラは常に関数宣言を選択します。 この曖昧性解消ルールは非直感的なことが多く、最も苛立たしいパースと呼ばれています。
#include <iterator>
#include <string>
#include <fstream>
int main()
{
std::ifstream file("data.txt");
// 以下は関数の宣言です。
std::string str(std::istreambuf_iterator<char>(file),
std::istreambuf_iterator<char>());
// これは、戻り値が std::string 型で、
// 第1引数が「file」という名前の std::istreambuf_iterator<char> 型で、
// 第2引数が名前のない std::istreambuf_iterator<char>() 型
// (関数ポインタ型 std::istreambuf_iterator<char>(*)() に書き直されます)
// の、 str という名前の関数を宣言します。
// C++11 前の修正方法。 いずれかの引数の周りに追加の括弧を付けます。
std::string str( (std::istreambuf_iterator<char>(file) ),
std::istreambuf_iterator<char>());
// C++11 後の修正方法。 いずれかの引数をリスト初期化にします。
std::string str(std::istreambuf_iterator<char>{file}, {});
}
同様に、関数形式のキャスト式 (3) を最も左の部分式として持つ式文と宣言文との間で曖昧な場合、その曖昧性はそれを宣言として扱うことによって解決されます。 この曖昧性解消は純粋に構文的なものです。 文中に現れる名前の意味は、それらが型名であるかどうか以外、考慮されません。
struct M { };
struct L { L(M&); };
M n;
void f() {
M(m); // 宣言。 M m; と同等です。
L(n); // ill-formed な宣言。
L(l)(m); // これでもまだ宣言です。
}
例
Run this code
#include <string>
#include <iostream>
#include <memory>
struct Foo {
int mem;
explicit Foo(int n) : mem(n) {}
};
int main()
{
std::string s1("test"); // const char* からのコンストラクタ。
std::string s2(10, 'a');
std::unique_ptr<int> p(new int(1)); // OK。 explicit コンストラクタが使用できます。
// std::unique_ptr<int> p = new int(1); // エラー。 コンストラクタが explicit です。
Foo f(2); // f は直接初期化されます。
// コンストラクタ引数 n は右辺値 2 からコピー初期化され、
// f.mem は引数 n から直接初期化されます。
// Foo f2 = 2; // エラー。 コンストラクタが explicit です。
std::cout << s1 << ' ' << s2 << ' ' << *p << ' ' << f.mem << '\n';
}
出力:
test aaaaaaaaaa 1 2