新規記事の投稿を行うことで、非表示にすることが可能です。
2017年12月10日
《その172》 派生クラスオブジェクトの初期化順序 & p.161演習4-2
派生クラスオブジェクトの初期化順序
派生クラスのコンストラクタが初期化作業を行うとき、その作業は次の順序で実行されます。
(1) 基底クラス部分オブジェクトの初期化
初期化作業は、基底クラスのコンストラクタに任せる。
(2) データメンバの初期化
クラス定義内で宣言された順に、データメンバが初期化される。
(3) コンストラクタ本体の実行
新版明解C++中級編 p.161 演習4-2
次のプログラムによって、派生クラスオブジェクトの初期化順序を確認することができる。
このプログラムを、デストラクタの起動順序を確認できるように書きかえたプログラムを作成せよ。
// ------------------------------------
#include <iostream>
using namespace std;
// クラス Derived の基底クラス
class Base {
int x;
public:
Base(int a = 0) : x(a) {
cout << "Base::x を" << x << "で初期化。\n";
}
};
// クラス Derived にメンバとして含まれるクラス
class Memb {
int x;
public:
Memb(int a = 0) : x(a) {
cout << "Memb::x を" << x << "で初期化。\n";
}
};
// 派生クラス Derived を生成
// (基底クラス Base から public派生)
class Derived : public Base {
int y;
Memb m1;
Memb m2;
void say() {
y = 0;
cout << "Derived::y を" << y << "で初期化。\n";
}
public:
Derived() { say(); }
Derived(int a, int b, int c)
: m2(a), m1(b), Base(c) {
say();
}
};
int main() {
Derived d1;
cout << '\n';
Derived d2(1, 2, 3);
}
// *** 実行結果 ****************
// * Base::x を0で初期化。 *
// * Memb::x を0で初期化。 *
// * Memb::x を0で初期化。 *
// * Derived::y を0で初期化。 *
// * *
// * Base::x を3で初期化。 *
// * Memb::x を2で初期化。 *
// * Memb::x を1で初期化。 *
// * Derived::y を0で初期化。 *
// *****************************
// ------------
◆以下が解答のプログラムと、その実行結果です。
// p161_演習4-2
#include <iostream>
using namespace std;
// クラス Derived の基底クラス
class Base {
int x;
public:
Base(int a = 0) : x(a) {
cout << "Base::x を" << x << "で初期化。\n";
}
~Base() { cout << "デストラクタ ~Base, "
<< "x = " << x << '\n';
}
};
// クラス Derived にメンバとして含まれるクラス
class Memb {
int x;
public:
Memb(int a = 0) : x(a) {
cout << "Memb::x を" << x << "で初期化。\n";
}
~Memb() { cout << "デストラクタ ~Memb, "
<< "x = " << x << '\n'; }
};
// 派生クラス Derived を生成
// (基底クラス Base から public派生)
class Derived : public Base {
int y;
Memb m1;
Memb m2;
void say() {
y = 0;
cout << "Derived::y を" << y << "で初期化。\n";
}
public:
Derived() { say(); }
Derived(int a, int b, int c)
: m2(a), m1(b), Base(c) {
say();
}
~Derived() { cout << "\nデストラクタ ~Derived, "
<< "y = " << y << '\n';
}
};
int main() {
Derived d1;
cout << '\n';
Derived d2(1, 2, 3);
}
この実行結果から、デストラクタの起動順序は、コンストラクタの初期化作業のときとは逆の順序であることが確認できます。
《その171》 継承とデフォルトコンストラクタ & p.159演習4-1
継承とデフォルトコンストラクタ
派生クラスにおいてコンストラクタが定義されていない場合は、コンパイラによって自動でデフォルトコンストラクタが定義されます。その際、『その派生クラスの直接基底クラスが"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていなければ、コンパイルエラーとなります。』
次の 演習4-1 は、そのことを確認するための問題です。
新版明解C++中級編 p.159 演習4-1
下記のプログラムで、クラス Base のコンストラクタを、次の二通り @, Aのコンストラクタに置きかえたプログラムを作成して、下の【重要事項】の内容を確認せよ。
@ Base::Base(int xx)
: x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
A Base::Base(int xx = 99)
: x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
【重要事項】コンストラクタを定義しないクラスは、そのクラスの直接基底クラスが"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていなければ、コンパイルエラーとなる。
// ------------------------------------
#include <iostream>
using namespace std;
class Base {
int x;
public:
Base() : x(99) { cout << "Base::x を 99 で初期化。\n"; }
// ↑のコンストラクタを書きかえること。
int get_x() const { return x; }
};
class Derived : public Base {
};
int main()
{
Derived d;
cout << "d.get_x() = " << d.get_x() << '\n';
}
// *** 実行結果 ******************
// * Base::x を 99 で初期化。 *
// * d.get_x() = 99 *
// *******************************
// ------------
◆以下が解答になります。
元々のコード
Base() : x(99) { cout << "Base::xを99で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっているので、コンパイルは成功します。
@のコード
Base::Base(int xx)
: x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっていないので、コンパイルエラーになります。
Aのコード
Base::Base(int xx = 99)
: x(xx) { cout << "Base::x を " << x << " で初期化。\n"; }
の場合、基底クラス Base は"引数を与えることなく呼び出せるデフォルトコンストラクタ"をもっているので、コンパイルは成功します。
以下は、Aの場合のプログラムと、その実行結果です。
// p159_演習4-1
#include <iostream>
using namespace std;
class Base {
int x;
public:
Base::Base(int xx = 99) : x(xx) {
cout << "Base::x を " << x << " で初期化。\n";
}
int get_x() const { return x; }
};
class Derived : public Base {
};
int main()
{
Derived d;
cout << "d.get_x() = " << d.get_x() << '\n';
}
《その170》 クラスの継承(3)
派生クラスのコンストラクタ
派生クラスの初期化時、基底クラスから継承したデータメンバの初期化は、基底クラスのコンストラクタに任せるようにします。次のプログラムで、派生クラス C_drvd のコンストラクタ内の赤文字部分です。
// ------------------------------------
#include <iostream>
using namespace std;
// 基底クラス C_base
class C_base {
int a;
int b;
public:
C_base(int x, int y) : a(x), b(y) { }
int get_a() const { return a; }
int get_b() const { return b; }
};
// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
public:
C_drvd(int x, int y, int z)
: C_base(x, y), k(z) { }
void disp() const {
cout << "a = " << get_a()
<< ", b = " << get_b()
<< ", k = " << k;
}
};
int main() {
C_drvd d(1, 2, 3);
d.disp();
cout << '\n';
}
// ------------------------------------
派生クラスのデフォルトコンストラクタ
派生クラスにコンストラクタの定義が無い場合は、デフォルトコンストラクタが自動的に定義されます。
よって、初期化時に値を与える必要がある基底クラスを親にして、コンストラクタの定義がない派生クラスを作ろうとするとエラーになります。
次のような場合です。
// ----------------------------
// 基底クラス C_base
class C_base {
int a;
public:
C_base(int x) : a(x) { }
// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
}; // エラー
// ----------------------------
コンパイラによって自動的に定義されるデフォルトコンストラクタ
C_drvd() { }
は、int a の初期化を、基底クラス C_base に任せますから、
C_base(int x) : a(x) { } // C_baseのコンストラクタ
が呼ばれることになります。
ところが、このコンストラクタは int x の値を与えないと呼び出せません。
このエラーは、基底クラス C_base のコンストラクタを、引数を与えなくても呼び出せるようにすれば、回避できます。
例えば、次のようにします(赤文字部分)。
// ----------------------------
// 基底クラス C_base
class C_base {
int a;
public:
C_base(int x = 0) : a(x) { }
};
// 派生クラス C_drvd
class C_drvd : public C_base {
int k;
}; // 大丈夫
// ----------------------------