C++ 基底クラスのコンストラクタ呼び出し【子から親クラスの初期化】

C++で派生クラス(子クラス)のコンストラクタから基底クラス(親クラス)のコンストラクタを明示的に呼び出すためには、派生クラス名() : 基底クラス名() {}という形式でコンストラクターを定義します。

struct Base {};
struct Derived : Base {
  Derived() : Base() {}
};

この基底クラス名()がコンストラクタ呼び出しの式に相当します。括弧内に実引数を指定することも可能です。より具体的な説明については、次項の# 基底クラスの初期化 を参考にしてください。

目次

基底クラスの初期化

コンストラクタ定義時の仮引数リストの後ろに:を記述し、続けて呼び出したい親クラスのコンストラクタ名と実引数をメンバ初期化子として記述することで、基底クラスのコンストラクタを明示的に呼び出すことができます。この記述はコンストラクタ初期化子やメンバ初期化子リストなどと呼ばれており、メンバ変数を初期化する際のものと同一ものとなっています。

struct A { A(int) {} }; // 基底クラス(親クラス)
struct B : A {          // 派生クラス(子クラス)
  B() : A(9) {} // コンストラクタ初期化子での明示的な呼び出し
};

B b; // `B()`と`A(int)`が呼び出される

基底クラスのコンストラクタ呼び出しは、派生クラスのコンストラクタ本体の処理が実行される前の段階で行われます。

struct A     { A()       { puts("A"); } };
struct B : A { B() : A() { puts("B"); } };
B b; // 出力結果:"AB"

コンストラクタ初期化子内の各メンバ初期化子はその並び順にかかわらず、まずは親クラスのコンストラクタが優先的に呼び出され、その後にメンバ変数の初期化作業が行われます。

struct A { A() { puts("A"); } };
struct B : A { int i; B() : i(puts("-")), A() { puts("B"); } };
B b; // 出力結果:"A-B"
参考:メンバ初期化子リスト|コンストラクタ初期化子/メンバ初期化子

デフォルトコンストラクタの暗黙呼び出し

派生クラスのコンストラクタから基底クラスのコンストラクタを明示的に呼び出さなかった場合には、基底クラスのデフォルトコンストラクタが暗黙的に呼び出されます。派生クラスのコンストラクタが暗黙的に定義された場合やdefault定義された場合も同様です。

struct A     { A() { puts("hello"); } };
struct B : A { B() {} };
struct C : A {};
struct D : A { D() = default; D(int) {} };
B();  // "hello"
C();  // "hello"
D();  // "hello"
D(9); // "hello"

ただし、基底クラス側でデフォルトコンストラクタ以外のコンストラクタ(A(int) {}など)を定義した場合には、デフォルトコンストラクタの暗黙的な定義が行われなくなってしまい、結果として派生クラスからデフォルトコンストラクタの暗黙的な呼び出しが行えなくなる点に注意が必要です。基底クラスのデフォルトコンストラクタをdelete定義した場合(A() = delete;)やプライベート宣言した場合も同様に不正な処理となり、コンパイル・エラーが発生します。

struct A { A(int) {} /* または A() = delete; */ };
struct B : A {
  B() {} // error: Constructor for 'B' must explicitly initialize the base class 'A' which does not have a default constructor
  B() : A(9) {} // OK
};
struct A { private: A() {} };
struct B : A {
  B() {} // error: Base class 'A' has private default constructor
};
struct C : A {};
C c; // Call to implicitly-deleted default constructor of 'struct C'

親クラスのコンストラクタを自動で継承する方法

継承コンストラクタ」という機能を用いることで、親クラスのコンストラクタを自動的に継承することも可能です。

struct A { A(int) { puts("A"); } };
struct B : A {
  using A::A; // 継承コンストラクタ
  // B(int v) : A(v) {} // こちらが不要になる
};

// 親クラスのコンストラクタを直接呼び出せるようになる
B(9); // 出力結果:"A"
参考:親クラスのコンストラクタを継承する方法【継承コンストラクタ】

コンストラクタ本体からの呼び出しに注意

なお、派生クラスのコンストラクタの処理部で、A()A::A()といった形で基底オブジェクトのコンストラクタを呼び出すことはできません。他の関数内の処理と同様に、別オブジェクトが生成されるだけです。

struct B : A {
  B() {
    A();    // 親オブジェクトのコンストラクタ呼び出しではない
    A::A(); // 単に一時オブジェクトが生成されるだけ
    auto a = A(); // こちらと同等の処理が行われる
  }
};

superキーワードによるコンストラクタ呼び出しは不可

Java等の多くのプログラミング言語では、コンストラクタ内の先頭でsuperキーワードを用いて親クラスのコンストラクタを呼び出すことが一般的ですが、C++の場合はその方法が取り入れられていないため留意しておいてください。

派生クラスでは基底クラスのメンバを初期化できない

派生クラス側のコンストラクタ初期化子で基底クラスのメンバを初期化することはできません。この場合はきちんと基底クラスのコンストラクタを通して初期化する必要があります。

struct A { int i; A() : i(0) {} A(int i) : i(i) {} };
struct B : A {
  B() : i(9) {}  // error: Member initializer 'i' does not name a non-static data member or base class
  B() : A(9) {}  // OK(きちんと親クラス経由で初期化)
  B() { i = 9; } // OK(再代入によるコストが余計に発生する)
};

コンストラクタ本体で代入処理を行うこともできますが、その場合は既にデフォルトコンストラクタ側で初期化が行われた後の処理という扱いになるため、余計な処理コストに繋がる場合があります。

参考:メンバ初期化子を省略した場合の挙動
広告