1 ポイント 投稿者 GN⁺ 2024-07-06 | 1件のコメント | WhatsAppで共有

コンストラクタがなく、初期化が必要

  • 序論

    • 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件のコメント

 
GN⁺ 2024-07-06
Hacker News のコメント
  • t の初期化結果は 0 になるはず

    • これは t が値初期化され、T にユーザー定義のデフォルトコンストラクタがないため、オブジェクトが 0 初期化された後にデフォルトコンストラクタが呼ばれるから
  • デフォルトコンストラクタはメンバーをデフォルト初期化し、値初期化とは異なる

  • GCC はこれに同意しているようだ

  • 投稿者は実際には x を値初期化していることを見落としていた

    • 結果は期待とは異なる
  • ルールの細部は複雑で、ときには不合理な部分もある

    • ただし、ほとんどの場合は期待した結果を得られる
  • デフォルト初期化を明示的に行えるようにすることは大きな改善になるはず

    • 値初期化が一般的なので、デフォルト初期化を望むときはコメントを書く必要がある
    • std::array<int, 100> = void; のような構文のほうがよいだろう
  • リスト初期化と集成体初期化のつながりは、集成体に対してリスト初期化を行うと集成体初期化が実行されること

    • ただし、リストに引数が 1 つだけある場合は直接初期化が行われる
  • 要素が 1 つの場合は、2 つ以上の要素の場合とは異なる動作をする

    • これは、パラメータパックから構造体を生成することがますます簡単になっている言語で起きている
  • 自分でコンストラクタを書けば、要素が 1 つだけ与えられたタプルや配列を初期化できる

    • ただし、特殊なケースでは誤ったコンストラクタが呼ばれることがある
  • C++11 の初期化リストが最初に登場したとき、これを見つけて狂っていると思った

  • "I Have No Mouth, and I Must Scream" (1967) への言及

  • T::T() = default; 構文を使用

  • 出力結果は 0 になると期待するが、実際にはゴミ値になるはず

    • すべてが完璧というわけにはいかない
  • ライブラリ利用者がライブラリの動作を変更できるようにしている

  • さらに多くの C++ の複雑さを求めるなら C++ FQA がおすすめ

    • 15 年たっても、C++ は古い機能や動作をほとんど削除しないため、今でも有効
  • ブログテーマは DEC 時代のコンピュータに着想を得ているが、すっきりしていてミニマル

    • 新鮮だ
  • これを読むと目まいがしてくる

    • Java のコンストラクタとオブジェクト初期化を理解しようとしていた記憶がよみがえる
  • Go と Rust には特別なコンストラクタがないため、多くの部分が単純になる

    • コンストラクタを使わなくなってから、コンストラクタが恋しくなったことがあるのか気になる
  • C++ のあらゆる暗黙の動作を表示するツールがあるのか気になる

    • たとえば、追加されるすべてのコンストラクタ、暗黙のコピーコンストラクタなど
  • クラスについて誤った情報を示している

    • コンストラクタを宣言した場合、デフォルトコンストラクタは提供されず、デフォルト初期化はコンパイラ診断とともに失敗する
  • T t; は「何もしない」という主張は誤り

    • 例のコードでは T t; は失敗する
  • ブログヘッダーに DEC のフロントパネルがある