初期化は、エンティティに最初の値を与えるか、参照の束縛を確立します。代入は、すでに存在するオブジェクトを変更します。コンストラクターは初期化にのみ関与し、代入演算子はライフタイムが開始した後にのみ関与するため、この違いはオーバーロード解決、不変条件、パフォーマンスに影響します。
Cの背景 #
Cの宣言には宣言子があり、それぞれに任意で = initializer が続きます。初期化子は代入式または波括弧付き初期化子リストです。入れ子になった波括弧は、入れ子になった集成体を初期化します。
struct point { int x; int y; } points[] = {
[0] = {.x = 1},
[2] = {.y = 4},
};Cの集成体初期化では、省略された要素は静的記憶域期間を持つかのように初期化されます。算術型の値はゼロになり、ポインター値はヌルポインターになり、入れ子になった集成体は再帰的に初期化されます。一方、初期化子なしで宣言された自動記憶域期間のスカラーは不定値を持ち、値が格納される前に読み取ってはなりません。
C++の形式と文法 #
C++では、宣言子に等号初期化子、波括弧付き初期化子、または丸括弧付き初期化子を付けられます。したがって、次の宣言は異なる形式になります。
T copy = expression; // コピー初期化
T direct(expression); // 直接初期化
T list{expression}; // 直接リスト初期化
T copy_list = {expression}; // コピーリスト初期化
braced-init-listは式ではなく、型も持ちません。宛先の型と初期化形式によって、オーバーロード解決、集成体の規則、縮小変換のチェックが決まります。宣言における T object() は空のオブジェクトではなく関数宣言です。オブジェクトには T object{}; を使用してください。
基本的な分類 #
ゼロ初期化 #
ゼロ初期化は、スカラー値をそれぞれのゼロ値に、ポインター値をヌルポインターに設定します。配列は要素ごとに処理されます。ゼロ初期化は、静的記憶域期間およびスレッド記憶域期間の初期化の一部として、また一部の値初期化の経路で発生します。参照は必ずオブジェクトまたは関数に束縛される必要があるため、参照に対する独立した記法ではありません。
static int requests; // 動的初期化の前にゼロ初期化される。
thread_local void* cache; // 動的初期化の前はヌルポインター。
デフォルト初期化 #
初期化子なしの宣言は、オブジェクトをデフォルト初期化します。クラスでは選択されたデフォルトコンストラクターが呼び出され、配列ではすべての要素がデフォルト初期化されます。自動記憶域期間または動的記憶域期間を持つスカラーには初期化が行われないため、代入されるまで安全に読み取ることはできません。
Widget widget; // Widget::Widget() を呼び出す。
int count; // 初期値なし。
値初期化 #
値初期化は、T{} や T() などのコンテキストで空の初期化子を使用します。スカラーはゼロになります。クラス型ではデフォルトコンストラクターが選択され、そのコンストラクターがユーザー提供でない場合は、コンストラクター本体の前にゼロ初期化が行われます。配列では各要素が値初期化されます。
int count{};
std::array<int, 4> flags{};コピー初期化、直接初期化、リスト初期化 #
コピー初期化では暗黙変換が許可されますが、explicit コンストラクターは選択されません。直接初期化では explicit コンストラクターを選択できます。直接リスト初期化でも選択できますが、コピーリスト初期化では、選択されたコンストラクターが explicit の場合は不適格です。
struct Port {
explicit Port(int);
};
Port a(443); // 直接初期化。
Port b{443}; // 直接リスト初期化。
// Port c = 443; // エラー: explicit コンストラクターは除外される。
リスト初期化では縮小変換が拒否されます。最初に、候補となる std::initializer_list コンストラクターが優先されます。このため、丸括弧の場合とは意図的に異なる結果になることがあります。
std::vector<int> repeated(3, 7); // 7, 7, 7
std::vector<int> elements{3, 7}; // 3, 7
// int truncated{3.5}; // エラー: 縮小変換。
初期化子リストの優先によって意味上の不一致が生じない場合は、縮小変換をチェックするために波括弧を使用してください。個数/値コンストラクターと値のリストを型が意図的に区別する場合は、丸括弧を使用してください。
集成体、配列、記憶域期間 #
配列は集成体です。波括弧による集成体初期化は宣言順に進み、省略された要素は該当する空の初期化子の規則に従って初期化されます。
int values[5]{1, 2}; // 残りの要素はゼロになる。
struct Endpoint {
std::string host;
int port;
};
Endpoint local{.host = "localhost", .port = 443}; // C++20の指示付き初期化。
C++20では、丸括弧による集成体初期化も許可されています。波括弧による集成体初期化とは異なり、縮小変換が許可され、参照要素に束縛された一時オブジェクトのライフタイムは延長されません。C++の指示付き初期化子はCより制約が多く、宣言順に直接の非静的データメンバーを指定します。C++には配列インデックス指定子がありません。
静的記憶域期間またはスレッド記憶域期間を持つオブジェクトは、宣言が該当する場合に定数初期化され、それ以外の場合は静的初期化によってゼロ初期化されます。すべての静的初期化は動的初期化より前に完了します。constinit は静的初期化を要求しますが、オブジェクトを const にはしません。constexpr は、その他の要件を満たす場合に定数評価で使用可能な値を宣言します。これらは記憶域と評価に関する保証であり、通常のオブジェクト初期化の代替ではありません。
参照とライフタイム #
参照初期化は別名を束縛し、2つ目のオブジェクトを作成しません。束縛形式と値カテゴリによって、一時オブジェクトのライフタイムが延長されるかどうかが決まります。
int value = 7;
int& writable = value;
const int& extended = 42; // 一時オブジェクトは `extended` と同じ期間存続する。
writable を介した代入は value を変更します。参照を再束縛することはありません。初期化をオブジェクトの不変条件が開始する時点として扱い、代入を別途規定された状態遷移として扱ってください。
コメント
GitHub で閲覧・コメントする(投稿にはログインが必要です)