[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
よくある質問
まとめ
この記事では、C++におけるコンストラクタの初期化子リストの重要性や、派生クラスと基底クラスのコンストラクタの関係、初期化子リストの具体的な応用例について詳しく解説しました。
特に、初期化子リストを使用することで、constメンバ変数や参照型メンバ変数の初期化が可能になり、効率的なプログラミングが実現できることがわかりました。
これを踏まえて、実際のプログラミングにおいて初期化子リストを積極的に活用し、より安全で効率的なコードを書くことをお勧めします。