初期化
変数の初期化 (Initialization) は、構築時にその初期値を提供します。
初期値は、宣言子またはnew式の初期化子セクションで提供されます。これは関数呼び出し中にも行われます。つまり、関数パラメータと関数の戻り値も初期化されます。
目次 |
[編集] 初期化子
各宣言子に対して、初期化子 (initializer) が存在する場合、以下のいずれかになります。
= expression |
(1) | ||||||||
= {}= { initializer-list }= { designated-initializer-list } |
(2) | (C++20以降) | |||||||
( expression-list )( initializer-list ) |
(3) | (C++11まで) (C++11以降) | |||||||
{}{ initializer-list }{ designated-initializer-list } |
(4) | (C++11以降) (C++11以降) (C++20以降) | |||||||
| 式 | - | 任意の式 (括弧で囲まれていないコンマ式を除く) |
| expression-list (式リスト) | - | コンマで区切られた式のリスト (括弧で囲まれていないコンマ式を除く) |
| initializer-list (初期化子リスト) | - | コンマで区切られた初期化子句 (initializer clause) のリスト (下記参照) |
| designated-initializer-list (指示付き初期化子リスト) | - | コンマで区切られた指示付き初期化子句のリスト |
初期化子句 (initializer clause) は、以下のいずれかです。
| 式 | (1) | ||||||||
{}
|
(2) | ||||||||
{ initializer-list } |
(3) | ||||||||
{ designated-initializer-list } |
(4) | (C++20以降) | |||||||
構文 (2-4) は、まとめて波括弧で囲まれた初期化子リスト (brace-enclosed initializer list) と呼ばれます。
[編集] 初期化子のセマンティクス
オブジェクトに初期化子が指定されていない場合、そのオブジェクトはデフォルト初期化されます。参照に初期化子が指定されていない場合、プログラムは不適格 (ill-formed) となります。
オブジェクトに指定された初期化子が () (構文上の制約により宣言子には現れない) の場合、オブジェクトは値初期化されます。参照に指定された初期化子が () の場合、プログラムは不適格となります。
初期化子のセマンティクスは以下の通りです。
- 初期化されるエンティティが参照である場合、参照の初期化を参照してください。
- それ以外の場合、初期化されるエンティティはオブジェクトです。オブジェクトの型を
Tとすると、
- 初期化子が構文 (1) の場合、オブジェクトはコピー初期化されます。
| (C++11まで) | |
|
(C++11以降) |
- 初期化子が構文 (3) の場合、オブジェクトは直接初期化されます。
#include <string> std::string s1; // default-initialization std::string s2(); // NOT an initialization! // actually declares a function “s2” // with no parameter and returns std::string std::string s3 = "hello"; // copy-initialization std::string s4("hello"); // direct-initialization std::string s5{'a'}; // list-initialization (since C++11) char a[3] = {'a', 'b'}; // aggregate initialization // (part of list initialization since C++11) char& c = a[0]; // reference initialization
[編集] 非ローカル変数
静的記憶域期間を持つすべての非ローカル変数は、プログラムの起動の一部として、main関数の実行が開始される前に初期化されます (下記で説明する遅延の場合を除く)。スレッドローカル記憶域期間を持つすべての非ローカル変数は、スレッドの起動の一部として、スレッド関数の実行が開始される前に順序付けられて初期化されます。これらの両方のクラスの変数について、初期化は2つの異なる段階で行われます。
[編集] 静的初期化
静的初期化には2つの形式があります。
実際には、
- 定数初期化は通常、コンパイル時に適用されます。事前に計算されたオブジェクト表現は、プログラムイメージの一部として格納されます。コンパイラがそうしない場合でも、初期化が動的初期化の前に行われることを保証しなければなりません。
- ゼロ初期化される変数は、プログラムイメージの
.bssセグメントに配置されます。これはディスク上で領域を占有せず、プログラムをロードする際にOSによってゼロで埋められます。
[編集] 動的初期化
すべての静的初期化が完了した後、非ローカル変数の動的初期化が以下の状況で行われます。
|
2) 部分順序付き動的初期化 (Partially-ordered dynamic initialization)。これは、暗黙的または明示的にインスタンス化された特殊化ではないすべてのインライン変数に適用されます。もし部分順序付きのVが、すべての翻訳単位において順序付きまたは部分順序付きのWの前に定義されている場合、Vの初期化はWの初期化の前に順序付けられます (プログラムがスレッドを開始する場合はhappens-before)。
|
(C++17以降) |
静的またはスレッド記憶域期間を持つ非ローカル変数の初期化が例外によって終了した場合、std::terminate が呼び出されます。
[編集] 早期動的初期化
コンパイラは、以下の両方の条件が真である場合、動的に初期化される変数を静的初期化の一部として (本質的にコンパイル時に) 初期化することが許可されています。
上記の規則のため、あるオブジェクト o1 の初期化が、動的初期化を必要とする可能性があり、かつ同じ翻訳単位で後から定義される名前空間スコープのオブジェクト o2 を参照する場合、使用される o2 の値が、完全に初期化された o2 の値 (コンパイラが o2 の初期化をコンパイル時に昇格させたため) なのか、単にゼロ初期化された o2 の値なのかは未規定です。
inline double fd() { return 1.0; } extern double d1; double d2 = d1; // unspecified: // dynamically initialized to 0.0 if d1 is dynamically initialized, or // dynamically initialized to 1.0 if d1 is statically initialized, or // statically initialized to 0.0 (because that would be its value // if both variables were dynamically initialized) double d1 = fd(); // may be initialized statically or dynamically to 1.0
[編集] 遅延動的初期化
動的初期化が、main関数の最初の文 (静的変数の場合) またはスレッドの初期関数 (スレッドローカル変数の場合) の前にhappens-beforeとなるか、あるいはそれ以降に遅延されるかは、実装定義です。
もし非インライン変数の(C++17以降)初期化がmain/スレッド関数の最初の文の後に遅延される場合、その初期化は、初期化される変数と同じ翻訳単位で定義された静的/スレッド記憶域期間を持つ任意の変数が最初にODR-useされる前に行われます。ある翻訳単位からどの変数も関数もODR-useされない場合、その翻訳単位で定義された非ローカル変数は決して初期化されない可能性があります (これはオンデマンドのダイナミックライブラリの振る舞いをモデル化しています)。しかし、翻訳単位から何かがODR-useされる限り、その初期化や破棄に副作用があるすべての非ローカル変数は、たとえプログラム中で使用されなくても初期化されます。
|
インライン変数の初期化が遅延される場合、その特定の変数が最初にODR-useされる前に行われます。 |
(C++17以降) |
// ============ // == File 1 == #include "a.h" #include "b.h" B b; A::A() { b.Use(); } // ============ // == File 2 == #include "a.h" A a; // ============ // == File 3 == #include "a.h" #include "b.h" extern A a; extern B b; int main() { a.Use(); b.Use(); } // If a is initialized before main is entered, b may still be uninitialized // at the point where A::A() uses it (because dynamic initialization is // indeterminately sequenced across translation units) // If a is initialized at some point after the first statement of main (which odr-uses // a function defined in File 1, forcing its dynamic initialization to run), // then b will be initialized prior to its use in A::A
[編集] 静的ローカル変数
ローカル (つまりブロックスコープ) の静的変数およびスレッドローカル変数の初期化については、静的ブロックスコープ変数を参照してください。
外部または内部リンケージを持つ変数のブロックスコープ宣言では、初期化子は許可されません。そのような宣言は extern と共に現れなければならず、定義であってはなりません。
[編集] クラスメンバ
非静的データメンバは、メンバ初期化子リストまたはデフォルトメンバ初期化子で初期化できます。
[編集] ノート
非ローカル変数の破棄の順序は std::exit で説明されています。
[編集] 欠陥報告
以下の動作変更を伴う欠陥報告が、以前に公開されたC++標準に遡って適用されました。
| DR | 適用対象 | 公開された動作 | 正しい動作 |
|---|---|---|---|
| CWG 270 | C++98 | クラステンプレートの静的データメンバの 初期化順序が未規定だった |
明示的な特殊化と定義を除き、 順序なしとして規定された |
| CWG 441 | C++98 | 静的記憶域期間を持つ非ローカル参照が 動的初期化の前に常に初期化されるとは限らなかった |
静的初期化と見なされ、常に 動的初期化の前に初期化されるようになった |
| CWG 1415 | C++98 | ブロックスコープの extern 変数 宣言が定義になり得た |
禁止された (そのような宣言では 初期化子が許可されない) |
| CWG 2599 | C++98 | 初期化子内の関数引数の評価が 初期化の一部であるかどうかが不明確だった |
初期化の一部であるとされた |
[編集] 関連項目
| C言語ドキュメント の 初期化
|