コンテンツにスキップ

最終更新:2026-10-12

C++ クラス初期化リストイディオム

  • constメンバの初期化はコンストラクタの初期化リストでのみ行うことができます
  • 参照メンバの初期化もコンストラクタの初期化リストでのみ行うことができます
  • オブジェクトメンバ(そのオブジェクトメンバのクラスがデフォルトコンストラクタを持たない場合)の初期化も、コンストラクタの初期化リストでのみ行うことができます

クラス間のネスト

第一の方法:初期化リストを使用する。

class Animal {
public:
    Animal() {
        std::cout << "Animal() is called" << std::endl;
    }

    Animal(const Animal &) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal &) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog {
public:
    Dog(const Animal &animal) : __animal(animal) {
        std::cout << "Dog(const Animal &animal) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

private:
    Animal __animal;
};

int main() {
    Animal animal;
    std::cout << std::endl;
    Dog d(animal);
    std::cout << std::endl;
    return 0;
}

実行結果:

Animal() is called

Animal (const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

上から順に分析していきます。

main関数内のAnimal animal;はデフォルトコンストラクタを呼び出します。

Dog d(animal);は次と等価です:

Animal __animal = animal;
実際にはコピーコンストラクタが呼び出されるため、次が出力されます:
Animal (const Animal &) is called
その後、Dogのコンストラクタ内の出力が表示されます。

最後にデストラクタが呼び出され、プログラムは終了します。

第二の方法:コンストラクタ内での代入によってオブジェクトを初期化する。

コンストラクタを次のように変更します:

Dog(const Animal &animal) {
    __animal = animal;
    std::cout << "Dog(const Animal &animal) is called" << std::endl;
}
このときの出力結果:
Animal() is called

Animal() is called
Animal & operator=(const Animal &) is called
Dog(const Animal &animal) is called

~Dog() is called
~Animal() is called
~Animal() is called

ここから次のことが分かります:

Dog d(animal);を呼び出すとき、次と等価です:

先にオブジェクトを定義してから代入するため、まずデフォルトコンストラクタが呼び出され、その後に=演算子のオーバーロード関数が呼び出されます。

// 假设之前已经有了animal对象
Animal __animal;
__animal = animal;

小まとめ

以上より、次のような結論が得られます:

  • クラス内に他の独自定義のclassやstructが含まれる場合、初期化リストを使うと、実際にはオブジェクトの作成と同時に初期化が行われます
  • 一方、クラス内で代入する方式は、先にオブジェクトを定義してから代入することと等価であり、一般にまずデフォルトコンストラクタが呼び出され、その後に=演算子のオーバーロード関数が呼び出されます。

デフォルトコンストラクタがない継承関係の場合

今度は上記の関係を継承に変更し、AnimalとDogのコンストラクタを次のコードのように修正することを考えます:

class Animal {
public:
    Animal(int age) {
        std::cout << "Animal(int age) is called" << std::endl;
    }

    Animal(const Animal & animal) {
        std::cout << "Animal (const Animal &) is called" << std::endl;
    }

    Animal &operator=(const Animal & amimal) {
        std::cout << "Animal & operator=(const Animal &) is called" << std::endl;
        return *this;
    }

    ~Animal() {
        std::cout << "~Animal() is called" << std::endl;
    }
};

class Dog : Animal {
public:
    Dog(int age) : Animal(age) {
        std::cout << "Dog(int age) is called" << std::endl;
    }

    ~Dog() {
        std::cout << "~Dog() is called" << std::endl;
    }

};

上記は初期化リストを通じて基底クラスの引数付きコンストラクタに引数を渡しています。初期化リストを使わずに渡した場合、どのような影響が生じるでしょうか?

初期化リストを取り除く

Dog(int age)  {
    std::cout << "Dog(int age) is called" << std::endl;
}
プログラムを実行します:

error: no matching function for call to ‘Animal::Animal()’

Animalにはデフォルトコンストラクタがないためエラーになります。この種の問題は致命的であり、できるだけ避けるべきです。初期化リストを使って基底クラスのコンストラクタを初期化することができます。

クラス内のconstデータメンバ・参照データメンバ

特に参照データメンバは、必ず初期化リストで初期化しなければならず、代入による初期化はできません!

例:上記のAnimalに非公開メンバを追加し、コンストラクタを次のように修正します:

class Animal {
public:
    Animal(int age,std::string name) {
        std::cout << "Animal(int age) is called" << std::endl;
    }
private:
    int &age_;
    const std::string name_;
};

次のようなエラーが出ます:

error: uninitialized reference member in ‘int&’

次のように変更すべきです:

Animal(int age, std::string name) : age_(age), name_(name) {
    std::cout << "Animal(int age) is called" << std::endl;
}

関連トピック

コメント