基底クラスおよびメンバーの初期化

C++コンストラクターは、次に示す 2 とおりの異なった方法でメンバーを初期化できます。 コンストラクターは渡された引き数を使用して、コンストラクター定義内のメンバー変数を 初期化することができます。

complx(double r, double i = 0.0) { re = r; im = i; }

またはコンストラクターは、定義の中に初期化指定子リスト を 含めることができますが、それらは、関数本体の前に置く必要があります。

complx(double r, double i = 0) : re(r), im(i) { /* ... */ }

どちらの方法でも、引き数値は適切なクラスのデータ・メンバーに 代入されます。

コンストラクター初期化指定子リストの構文を次に示します。

      .-,---------------------------------------------------.
      |                    .---------------------------.    |
      V                    V                           |    |
>>-:----+-identifier-+--(----+-----------------------+-+--)-+--><
        '-class_name-'       '-assignment_expression-'
 
 

コンストラクター宣言の一部ではなく、 関数定義の一部として初期化リストをインクルードします。次に例を示します。

#include <iostream>
using namespace std;
 
class B1 {
 int b;
public:
  B1() { cout << "B1::B1()" << endl; };
 
  // inline constructor
  B1(int i) : b(i) { cout << "B1::B1(int)" << endl; }
};
class B2 {
 int b;
protected:
  B2() { cout << "B2::B2()" << endl; }
 
  // noninline constructor
  B2(int i);
};
 
// B2 constructor definition including initialization list
B2::B2(int i) : b(i) { cout << "B2::B2(int)" << endl; }
 
class D : public B1, public B2 {
  int d1, d2;
public:
  D(int i, int j) : B1(i+1), B2(), d1(i) {
    cout << "D1::D1(int, int)" << endl;
    d2 = j;}
};
 
int main() {
  D obj(1, 2);
}

次に、上記の例の出力を示します。

B1::B1(int)
B1::B1()
D1::D1(int, int)

コンストラクターのある基底クラスまたはメンバーをコンストラクターを呼び出すことによって、 明示的に初期化するのでない場合、コンパイラーは、 自動的にデフォルト・コンストラクターのある基底クラスまたはメンバーを初期化します。 上記の例では、クラス D のコンストラクター内の呼び出し B2() を除外すると (後に示すように)、空の式リストのあるコンストラクター初期化指定子が自動的に作成されて、B2 を初期化します。 クラス D のコンストラクター (上記と下記に示されています) は、クラス D のオブジェクトと同じ構造体になります。

class D : public B1, public B2 {
  int d1, d2;
public:
 
  // call B2() generated by compiler
  D(int i, int j) : B1(i+1), d1(i) {
    cout << "D1::D1(int, int)" << endl;
    d2 = j;}
};

上記の例では、コンパイラーは、B2() のデフォルトのコンストラクターを自動的に呼び出します。

派生クラスがコンストラクターを呼び出すことができるようにするには、 コンストラクターを public または protected 付きで宣言する必要があります。 次に例を示します。

class B {
  B() { }
};
 
class D : public B {
 
  // error: implicit call to private B() not allowed
  D() { }
};

コンパイラーは、コンストラクターが private コンストラクター B::B() にアクセスできないので、D::D() の定義を許可しません。

初期化指定子リストを指定して、次のことを初期化する必要があります。それらは、 デフォルト・コンストラクターのない基底クラス、参照データ・メンバー、 非静的 const データ・メンバー、または定数データ・メンバーを含むクラス・タイプです。 次の例は、このことを示しています。

class A {
public:
  A(int) { }
};
 
class B : public A {
  static const int i;
  const int j;
  int &k;
public:
  B(int& arg) : A(0), j(1), k(arg) { }
};
 
int main() {
  int x = 0;
  B obj(x);
};

データ・メンバー j および k、 さらに基底クラス A は、B のコンストラクターの初期化指定子リストで初期化される必要があります。

クラスのメンバーを初期化する際、データ・メンバーを使用できます。 次の宣言は、このことを示しています。

struct A {
  int k;
  A(int i) : k(i) { }
};
struct B: A {
  int x;
  int i;
  int j;
  int& r;
  B(int i): r(x), A(i), j(this->i), i(i) { }
};

コンストラクター B(int i) は、次のことを初期化します。

クラスのメンバーを初期化する場合、メンバー関数 (仮想メンバー関数を含む) を呼び出したり、あるいは演算子 typeid または dynamic_cast を使用することもできます。 しかし、すべての基底クラスが初期化される前に、 メンバー初期化リストにあるこれらの演算を実行する場合、その振る舞いは、未定義です。 次の例は、このことを示しています。

#include <iostream>
using namespace std;
 
struct A {
  int i;
  A(int arg) : i(arg) {
    cout << "Value of i: " << i << endl;
  }
};
 
struct B : A {
  int j;
  int f() { return i; }
  B();
};
 
B::B() : A(f()), j(1234) {
   cout << "Value of j: " << j << endl;
}
 
int main() {
  B obj;
}

上記の例の出力は、次の出力に類似しています。

Value of i: 8
Value of j: 1234
B のコンストラクターの初期化指定子 A(f()) の振る舞いは、未定義です。 ランタイムは、B::f() を呼び出し、基底 A が初期化されていなくても、A::i にアクセスしようとします。

次の例は、B::B() の初期化指定子が異なる引き数を持つ点を除いては、直前の例と同じです。

#include <iostream>
using namespace std;
 
struct A {
  int i;
  A(int arg) : i(arg) {
    cout << "Value of i: " << i << endl;
  }
};
 
struct B : A {
  int j;
  int f() { return i; }
  B();
};
 
B::B() : A(5678), j(f()) {
   cout << "Value of j: " << j << endl;
}
 
int main() {
  B obj;
}

以下に、上の例の出力を示します。

Value of i: 5678
Value of j: 5678
B のコンストラクターでは、初期化指定子 j(f()) の振る舞いは、明確に定義されています。 B::j が初期化される時、基底クラス A も初期化済みです。

関連参照

IBM Copyright 2003