[C++] コンストラクタの初期化子リストとは?派生元コンストラクタの呼び出し方も解説

C++におけるコンストラクタの初期化子リストとは、コンストラクタの本体が実行される前にメンバ変数や基底クラスのコンストラクタを初期化するための構文です。

コンストラクタの定義の後にコロン : を使い、初期化するメンバ変数や基底クラスを指定します。

これにより、メンバ変数のデフォルトコンストラクタを呼び出すことなく、直接初期化が可能です。

派生クラスのコンストラクタで基底クラスのコンストラクタを呼び出すには、初期化子リストで基底クラス名を指定します。

例えば、Baseクラスのコンストラクタを呼び出す場合、Derivedクラスのコンストラクタで Base(args) のように記述します。

この記事でわかること
  • コンストラクタの初期化子リストの役割
  • 基底クラスのコンストラクタ呼び出し方法
  • 参照型やconst型の初期化方法
  • 複数の基底クラスの初期化手法
  • 初期化子リストの使用上の注意点

目次から探す

コンストラクタの初期化子リストとは

C++におけるコンストラクタの初期化子リストは、オブジェクトのメンバ変数を初期化するための特別な構文です。

これにより、メンバ変数をコンストラクタの本体が実行される前に初期化することができます。

初期化子リストは、特に参照型やconst型のメンバ変数を持つクラスで重要です。

コンストラクタの初期化子リストの基本構文

初期化子リストは、コンストラクタの定義の後にコロン(:)を使って記述します。

以下はその基本構文です。

class MyClass {
public:
    MyClass(int value) : memberVariable(value) {
        // コンストラクタの本体
    }
private:
    int memberVariable;
};

この例では、memberVariableが初期化子リストを使ってvalueで初期化されています。

初期化子リストを使う理由

初期化子リストを使用する主な理由は以下の通りです。

  • 効率性: メンバ変数を直接初期化するため、余分な代入操作が不要です。
  • constや参照型の初期化: constメンバ変数や参照型のメンバ変数は、初期化時に値を設定する必要があります。
  • 基底クラスの初期化: 派生クラスのコンストラクタで基底クラスのコンストラクタを呼び出す際に使用します。

メンバ変数の初期化と代入の違い

メンバ変数の初期化と代入には重要な違いがあります。

スクロールできます
特徴初期化代入
実行タイミングコンストラクタ実行前コンストラクタ実行後
constメンバ変数初期化可能代入不可
参照型メンバ変数初期化可能代入不可

初期化は、オブジェクトが生成される際に行われるため、constや参照型のメンバ変数を正しく設定するためには初期化子リストが必要です。

初期化子リストで初期化できるもの

初期化子リストでは、以下のものを初期化できます。

  • 基本データ型(int, float, etc.)
  • クラス型のメンバ変数
  • constメンバ変数
  • 参照型メンバ変数
  • 基底クラスのメンバ

初期化子リストを使う際の注意点

初期化子リストを使用する際には、以下の点に注意が必要です。

  • 順序: メンバ変数は、クラス内で宣言された順序で初期化されます。

初期化子リストの順序は無視されます。

  • 初期化の失敗: 初期化子リストでの初期化が失敗すると、コンストラクタは例外を投げることがあります。
  • デフォルトコンストラクタ: メンバ変数にデフォルトコンストラクタがある場合、初期化子リストを使わずに初期化することも可能ですが、明示的に初期化することが推奨されます。

派生クラスと基底クラスのコンストラクタ

C++では、クラスの継承を利用して派生クラスを作成することができます。

派生クラスのコンストラクタは、基底クラスのコンストラクタを呼び出して、基底クラスのメンバ変数を初期化する必要があります。

ここでは、派生クラスと基底クラスのコンストラクタに関する重要なポイントを解説します。

派生クラスのコンストラクタで基底クラスを初期化する方法

派生クラスのコンストラクタで基底クラスを初期化するには、初期化子リストを使用します。

以下の例を見てみましょう。

#include <iostream>
using namespace std;
class Base {
public:
    Base(int value) {
        cout << "基底クラスのコンストラクタ: " << value << endl;
    }
};
class Derived : public Base {
public:
    Derived(int value) : Base(value) {
        cout << "派生クラスのコンストラクタ" << endl;
    }
};
int main() {
    Derived obj(10);
    return 0;
}

このコードでは、DerivedクラスのコンストラクタがBaseクラスのコンストラクタを初期化子リストを使って呼び出しています。

基底クラスのコンストラクタ: 10
派生クラスのコンストラクタ

基底クラスのコンストラクタ呼び出しの順序

C++では、オブジェクトが生成される際、基底クラスのコンストラクタが先に呼び出され、その後に派生クラスのコンストラクタが呼び出されます。

この順序は、基底クラスのメンバ変数が派生クラスのコンストラクタが実行される前に初期化されることを保証します。

基底クラスのデフォルトコンストラクタと引数付きコンストラクタ

基底クラスにはデフォルトコンストラクタと引数付きコンストラクタの両方を定義できます。

派生クラスのコンストラクタで基底クラスのデフォルトコンストラクタを呼び出す場合、初期化子リストを省略できます。

class Base {
public:
    Base() {
        cout << "基底クラスのデフォルトコンストラクタ" << endl;
    }
};
class Derived : public Base {
public:
    Derived() {
        cout << "派生クラスのデフォルトコンストラクタ" << endl;
    }
};

一方、引数付きコンストラクタを使用する場合は、初期化子リストで基底クラスのコンストラクタを明示的に呼び出す必要があります。

派生クラスで基底クラスのコンストラクタを明示的に呼び出す理由

派生クラスで基底クラスのコンストラクタを明示的に呼び出す理由は、基底クラスのメンバ変数を適切に初期化するためです。

基底クラスのコンストラクタが必要な引数を持つ場合、派生クラスのコンストラクタでその引数を指定しなければ、コンパイルエラーが発生します。

仮想基底クラスの初期化

仮想基底クラスを使用する場合、派生クラスのコンストラクタで仮想基底クラスのコンストラクタを初期化する必要があります。

仮想基底クラスは、複数の派生クラスが同じ基底クラスを持つ場合に、二重継承の問題を解決するために使用されます。

以下の例を見てみましょう。

#include <iostream>
using namespace std;
class Base {
public:
    Base() {
        cout << "仮想基底クラスのコンストラクタ" << endl;
    }
};
class Derived1 : virtual public Base {
public:
    Derived1() {
        cout << "派生クラス1のコンストラクタ" << endl;
    }
};
class Derived2 : virtual public Base {
public:
    Derived2() {
        cout << "派生クラス2のコンストラクタ" << endl;
    }
};
class FinalDerived : public Derived1, public Derived2 {
public:
    FinalDerived() {
        cout << "最終派生クラスのコンストラクタ" << endl;
    }
};
int main() {
    FinalDerived obj;
    return 0;
}

このコードでは、FinalDerivedクラスのコンストラクタが呼び出されると、仮想基底クラスのコンストラクタが最初に呼び出され、その後に派生クラスのコンストラクタが呼び出されます。

仮想基底クラスのコンストラクタ
派生クラス1のコンストラクタ
派生クラス2のコンストラクタ
最終派生クラスのコンストラクタ

初期化子リストの応用例

初期化子リストは、C++のコンストラクタにおいて非常に重要な役割を果たします。

ここでは、初期化子リストの具体的な応用例をいくつか紹介します。

メンバ変数が参照型の場合の初期化

参照型のメンバ変数は、初期化時に必ず値を設定する必要があります。

初期化子リストを使用することで、参照型のメンバ変数を適切に初期化できます。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass(int& ref) : memberRef(ref) {
        // コンストラクタの本体
    }
    
    void display() {
        cout << "参照型メンバ変数の値: " << memberRef << endl;
    }
    
private:
    int& memberRef; // 参照型メンバ変数
};
int main() {
    int value = 20;
    MyClass obj(value);
    obj.display();
    return 0;
}
参照型メンバ変数の値: 20

メンバ変数がconstの場合の初期化

constメンバ変数は、初期化時にのみ値を設定できるため、初期化子リストを使用して初期化する必要があります。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass(int value) : constMember(value) {
        // コンストラクタの本体
    }
    
    void display() {
        cout << "constメンバ変数の値: " << constMember << endl;
    }
    
private:
    const int constMember; // constメンバ変数
};
int main() {
    MyClass obj(30);
    obj.display();
    return 0;
}
constメンバ変数の値: 30

メンバ変数がクラス型の場合の初期化

クラス型のメンバ変数も初期化子リストを使用して初期化することができます。

これにより、クラス型のメンバ変数のコンストラクタが呼び出されます。

#include <iostream>
using namespace std;
class InnerClass {
public:
    InnerClass(int value) : innerValue(value) {}
    
    void display() {
        cout << "InnerClassの値: " << innerValue << endl;
    }
    
private:
    int innerValue;
};
class OuterClass {
public:
    OuterClass(int value) : innerObject(value) {
        // コンストラクタの本体
    }
    
    void display() {
        innerObject.display();
    }
    
private:
    InnerClass innerObject; // クラス型メンバ変数
};
int main() {
    OuterClass obj(40);
    obj.display();
    return 0;
}
InnerClassの値: 40

複数の基底クラスを持つ場合の初期化

複数の基底クラスを持つ派生クラスでは、各基底クラスのコンストラクタを初期化子リストで呼び出す必要があります。

#include <iostream>
using namespace std;
class Base1 {
public:
    Base1(int value) {
        cout << "Base1のコンストラクタ: " << value << endl;
    }
};
class Base2 {
public:
    Base2(int value) {
        cout << "Base2のコンストラクタ: " << value << endl;
    }
};
class Derived : public Base1, public Base2 {
public:
    Derived(int value1, int value2) : Base1(value1), Base2(value2) {
        // コンストラクタの本体
    }
};
int main() {
    Derived obj(50, 60);
    return 0;
}
Base1のコンストラクタ: 50
Base2のコンストラクタ: 60

メンバ変数のデフォルト値と初期化子リストの併用

メンバ変数にデフォルト値を設定しつつ、初期化子リストを使用して特定の値で初期化することも可能です。

#include <iostream>
using namespace std;
class MyClass {
public:
    MyClass(int value) : memberVariable(value), defaultVariable(100) {
        // コンストラクタの本体
    }
    
    void display() {
        cout << "メンバ変数の値: " << memberVariable << endl;
        cout << "デフォルト値のメンバ変数の値: " << defaultVariable << endl;
    }
    
private:
    int memberVariable; // 初期化子リストで初期化
    int defaultVariable = 100; // デフォルト値
};
int main() {
    MyClass obj(70);
    obj.display();
    return 0;
}
メンバ変数の値: 70
デフォルト値のメンバ変数の値: 100

よくある質問

初期化子リストを使わないとどうなる?

初期化子リストを使わずにメンバ変数を初期化すると、以下のような問題が発生する可能性があります。

  • constメンバ変数や参照型メンバ変数の初期化失敗: constメンバ変数や参照型メンバ変数は、初期化時に必ず値を設定する必要があります。

初期化子リストを使わない場合、これらのメンバ変数は初期化できず、コンパイルエラーが発生します。

  • 効率の低下: メンバ変数を初期化子リストで初期化せず、コンストラクタの本体で代入する場合、初期化と代入の2回の操作が必要になります。

これにより、パフォーマンスが低下する可能性があります。

初期化子リストで例外が発生した場合はどうなる?

初期化子リストで例外が発生した場合、以下のような挙動が見られます。

  • コンストラクタの実行が中断される: 初期化子リスト内で例外が発生すると、その時点でコンストラクタの実行が中断され、オブジェクトは生成されません。
  • リソースの解放: 例外が発生した場合、すでに初期化されたメンバ変数やリソースは自動的に解放されます。

これにより、メモリリークを防ぐことができます。

初期化子リストでメンバ変数の順序は重要?

はい、初期化子リストでメンバ変数の順序は重要です。

C++では、メンバ変数はクラス内で宣言された順序で初期化されます。

初期化子リストの順序は無視されるため、以下の点に注意が必要です。

  • 依存関係: あるメンバ変数が他のメンバ変数に依存している場合、依存先のメンバ変数が先に初期化されるように、クラス内での宣言順序を考慮する必要があります。
  • 初期化の順序: 初期化子リストでの順序を変更しても、実際の初期化順序はクラス内の宣言順序に従うため、意図した通りに初期化されないことがあります。

これにより、予期しない動作を引き起こす可能性があります。

まとめ

この記事では、C++におけるコンストラクタの初期化子リストの重要性や、派生クラスと基底クラスのコンストラクタの関係、初期化子リストの具体的な応用例について詳しく解説しました。

特に、初期化子リストを使用することで、constメンバ変数や参照型メンバ変数の初期化が可能になり、効率的なプログラミングが実現できることがわかりました。

これを踏まえて、実際のプログラミングにおいて初期化子リストを積極的に活用し、より安全で効率的なコードを書くことをお勧めします。

当サイトはリンクフリーです。出典元を明記していただければ、ご自由に引用していただいて構いません。

関連カテゴリーから探す

  • URLをコピーしました!
目次から探す