コンストラクタなしの初期化が必要な理由
(consteval.ca)コンストラクタがなく、初期化が必要
-
序論
- C++を初めて学んだとき、コンパイラがデフォルトコンストラクタを提供する場合について学んだ。
- 特定の状況では、オブジェクトが初期化されないままになる危険性について考えるようになった。
-
デフォルト初期化と値初期化
T t;はデフォルト初期化を行う。Tがクラス型でデフォルトコンストラクタを持っていれば実行される。Tが配列型なら各要素をデフォルト初期化する。- それ以外では何もしない。
T t{};は値初期化を行う。Tがクラス型なら、デフォルトコンストラクタがないか、ユーザー提供または削除されたデフォルトコンストラクタがある場合はデフォルト初期化する。- それ以外では 0 で初期化した後にデフォルト初期化する。
Tが配列型なら各要素を値初期化する。- それ以外では 0 で初期化する。
-
デフォルトコンストラクタ
- デフォルトコンストラクタを宣言しないと、コンパイラが暗黙的にデフォルトコンストラクタを宣言する。
- 暗黙的に宣言されたデフォルトコンストラクタは、空の本体と空のメンバ初期化リストを持つ。
- 例:
struct T { int x; T() = default; }; T t{}; std::cout << t.x << std::endl; // 出力結果は 0
-
暗黙的に定義されたデフォルトコンストラクタ
- デフォルトコンストラクタが暗黙的に宣言されるか、明示的に
= defaultとして宣言されると、コンパイラは暗黙的に定義されたデフォルトコンストラクタを提供する。 - 例:
struct T { T(); }; T::T() = default; T t{}; std::cout << t.x << std::endl; // 出力結果はゴミ値
- デフォルトコンストラクタが暗黙的に宣言されるか、明示的に
-
デフォルトコンストラクタを提供できない場合
Tが非静的参照メンバを持っている場合Tがデフォルト構築できない、または破棄できない非静的メンバもしくは非抽象基底クラスを持っている場合Tがデフォルトメンバ初期化子を持たないconst非静的メンバを持っている場合
-
正しい初期化
T t{};はリスト初期化を行う。- リスト初期化は、直接リスト初期化とコピーリスト初期化に分かれる。
- 例:
struct S { int a; float b; char c; }; S s{3, 4.0f, 'S'}; // コンストラクタ呼び出しなし
-
リスト初期化とアグリゲート初期化
- アグリゲート初期化はリスト初期化の特別な形で、クラスまたは配列の各要素を初期化リストの各要素でコピー初期化する。
- 例:
struct A { const int x; }; A a{}; // a.x は 0 で初期化される
-
丸括弧を使った初期化
- 丸括弧を使った初期化は直接非リスト初期化を行う。
- 例:
struct T { const int& r; }; T t(42); // t.r は 42 を指す参照
-
要約
- 初期化ルールは複雑だが、自分でコンストラクタを書けば大半の問題は避けられる。
- コンパイラ任せにせず、自分でコンストラクタを書くのがよい。
GN⁺の意見
- この記事は C++ の初期化ルールの複雑さをうまく説明している。
- C++ の初期化ルールを理解することは重要であり、コードの安定性と性能に大きく影響する。
- 自分でコンストラクタを書くことが、初期化の問題を避ける最善の方法だ。
- 類似の機能を持つ別の言語として Rust があり、Rust は初期化ルールがより明確である。
- 新しい技術を採用する際は、初期化ルールのような細部を十分に理解して使うことが重要である。
1件のコメント
Hacker News のコメント
tの初期化結果は 0 になるはずtが値初期化され、Tにユーザー定義のデフォルトコンストラクタがないため、オブジェクトが 0 初期化された後にデフォルトコンストラクタが呼ばれるからデフォルトコンストラクタはメンバーをデフォルト初期化し、値初期化とは異なる
GCC はこれに同意しているようだ
投稿者は実際には
xを値初期化していることを見落としていたルールの細部は複雑で、ときには不合理な部分もある
デフォルト初期化を明示的に行えるようにすることは大きな改善になるはず
std::array<int, 100> = void;のような構文のほうがよいだろうリスト初期化と集成体初期化のつながりは、集成体に対してリスト初期化を行うと集成体初期化が実行されること
要素が 1 つの場合は、2 つ以上の要素の場合とは異なる動作をする
自分でコンストラクタを書けば、要素が 1 つだけ与えられたタプルや配列を初期化できる
C++11 の初期化リストが最初に登場したとき、これを見つけて狂っていると思った
"I Have No Mouth, and I Must Scream" (1967) への言及
T::T() = default;構文を使用出力結果は 0 になると期待するが、実際にはゴミ値になるはず
ライブラリ利用者がライブラリの動作を変更できるようにしている
さらに多くの C++ の複雑さを求めるなら C++ FQA がおすすめ
ブログテーマは DEC 時代のコンピュータに着想を得ているが、すっきりしていてミニマル
これを読むと目まいがしてくる
Go と Rust には特別なコンストラクタがないため、多くの部分が単純になる
C++ のあらゆる暗黙の動作を表示するツールがあるのか気になる
クラスについて誤った情報を示している
T t;は「何もしない」という主張は誤りT t;は失敗するブログヘッダーに DEC のフロントパネルがある