今度はデストラクタ関数の方を確認してみます。
1つサンプルを作って試しましょう。
<sample program cpp078-01>
#include <iostream> class Base { public: Base(); ~Base(); }; class Derived : public Base { public: Derived(); ~Derived(); }; int main() { Derived derived; return 0; } Base::Base() { std::cout << "基底クラスのコンストラクタ関数" << std::endl; } Base::~Base() { std::cout << "基底クラスのデストラクタ関数" << std::endl; } Derived::Derived() { std::cout << "派生クラスのコンストラクタ関数" << std::endl; } Derived::~Derived() { std::cout << "派生クラスのデストラクタ関数" << std::endl; } |
<実行結果>
基底クラスのコンストラクタ関数 派生クラスのコンストラクタ関数 派生クラスのデストラクタ関数 基底クラスのデストラクタ関数 続行するには何かキーを押してください・・・
コンストラクタ関数とは逆に「派生クラス」、「基底クラス」の順番でデストラクタ関数が呼び出されました。
「派生クラス」を「基底クラス」の参照やポインタ変数に入れて使う事はよくある事です。
ただ、気を付けなければならない状況がありますので、それを説明しようと思います。
上のプログラムのmain関数を書き換えましょう。
int main() { Derived *pDerived = new Derived; Base *pBase = pDerived; delete pBase; return 0; } |
まず、「派生クラス」のポインタを用意し、newで領域を確保しました。
「基底クラス」のポインタを用意し、「派生クラス」のアドレスを格納しています。
領域の解放は「基底クラス」で行いました。
「派生クラス」で確保した領域のアドレスを「基底クラス」のポインタに入れていますので、解放されるアドレスは同じような気がします。
実際に実行して結果を見てみましょう。
<実行結果>
基底クラスのコンストラクタ関数 派生クラスのコンストラクタ関数 基底クラスのデストラクタ関数 続行するには何かキーを押してください・・・
「派生クラス」」のデストラクタ関数が呼ばれていません。
確保の時は「派生クラス」で確保しましたが、解放の時は「基底クラス」だったので呼ばれなかったのです。
「派生クラス」のデストラクタに大事なプログラムが書いてあった場合、非常に危険な状態になります。
そこで、これに対応するため↓のようにします。
<sample program cpp078-02>
#include <iostream>
class Base {
public:
Base();
virtual ~Base();
};
class Derived : public Base {
public:
Derived();
~Derived();
};
int main()
{
Derived *pDerived = new Derived;
Base *pBase = pDerived;
delete pBase;
return 0;
}
Base::Base()
{
std::cout << "基底クラスのコンストラクタ関数" << std::endl;
}
Base::~Base()
{
std::cout << "基底クラスのデストラクタ関数" << std::endl;
}
Derived::Derived()
{
std::cout << "派生クラスのコンストラクタ関数" << std::endl;
}
Derived::~Derived()
{
std::cout << "派生クラスのデストラクタ関数" << std::endl;
}
|
<実行結果>
基底クラスのコンストラクタ関数 派生クラスのコンストラクタ関数 派生クラスのデストラクタ関数 基底クラスのデストラクタ関数 続行するには何かキーを押してください・・・
「基底クラス」のデストラクタ関数に「virtual」キーワードを付けました。
実行結果を見ると、ちゃんと「派生クラス」のデストラクタ関数が呼ばれています。
このような使い方をする場合、「基底クラス」のデストラクタ関数を「仮想デストラクタ」にしておく必要があります。