# はじめに
Effective C++ 第3版の7項34ページから勉強していきます。
今回は、「ポリモーフィズムのための基底クラスには仮想デストラクタを宣言しよう」についです。
Effective C++ 第3版 - 6項 ポリモーフィズムのための基底クラスには仮想デストラクタを宣言しよう -
前置き
今回は、仮想デストラクタについて勉強します。
今回の勉強内容
基底クラスと派生クラス
以下に示すように、BaseAという基底クラスを継承したChildAというクラスを扱います。
class BaseA { // 基底クラス
public:
BaseA() { std::cout << "BaseA : コンストラクタ" << std::endl; }
~BaseA() { std::cout << "BaseA : デストラクタ" << std::endl; }
};
class ChildA : public BaseA { // 派生クラス
public:
ChildA() { std::cout << "ChildA : コンストラクタ" << std::endl; }
~ChildA() { std::cout << "ChildA : デストラクタ" << std::endl; }
};
この(上の)ように、クラスを定義すると、下に示すように、基底クラスのポインタ型で派生クラスのオブジェクトを動的に生成することができます。
BaseA *a1 = new BaseA(); // BaseAのポインタ型でBaseA型のオブジェクトを動的に生成
BaseA *a2 = new ChildA(); // BaseAのポインタ型でChildA型のオブジェクトを動的に生成
delete a1; // a1オブジェクトの破棄、メモリの開放
delete a2; // a2オブジェクトの破棄、メモリの開放
以下に実行後の出力結果を示します。
以下の実行後の出力結果から、 ChildAのデストラクタが実行されていないことが分かります。
このように、派生クラス(ChildA)のオブジェクトを破棄するのに、「仮想デストラクタを持たない基底クラス(BaseA)」のポインタにdeleteを適用すると、オブジェクトの派生クラス部分が動的に破棄されないといった問題があります。
BaseA : コンストラクタ
BaseA : コンストラクタ
ChildA : コンストラクタ
BaseA : デストラクタ
BaseA : デストラクタ
この問題を避けるために、基底クラスにデストラクタを与えます。
以下に、そのプログラムと実行後の出力結果を示します。
class BaseA { // 基底クラス
public:
BaseA() { std::cout << "BaseA : コンストラクタ" << std::endl; }
virtual ~BaseA() { std::cout << "BaseA : デストラクタ" << std::endl; } // 変更箇所
};
class ChildA : public BaseA { // 派生クラス
public:
ChildA() { std::cout << "ChildA : コンストラクタ" << std::endl; }
~ChildA() { std::cout << "ChildA : デストラクタ" << std::endl; }
};
BaseA *a1 = new BaseA(); // BaseAのポインタ型でBaseA型のオブジェクトを動的に生成
BaseA *a2 = new ChildA(); // BaseAのポインタ型でChildA型のオブジェクトを動的に生成
delete a1; // a1オブジェクトの破棄、メモリの開放
delete a2; // a2オブジェクトの破棄、メモリの開放
BaseA : コンストラクタ
BaseA : コンストラクタ
ChildA : コンストラクタ
BaseA : デストラクタ
ChildA : デストラクタ
BaseA : デストラクタ
実行後の出力結果から、今回のプログラムはChildAのデストラクタが実行されていることが分かります。
つまり、基底クラスのポインタにdeleteを適用しても、そのポインタが指し示す実際のオブジェクトが破棄できます。
仮想デストラクタを付ける基準
仮想デストラクタは、基底クラスでかつ、仮想関数を持つ場合に宣言する必要があります。
一方、仮想関数を持たない(基底)クラスに仮想デストラクタを宣言するのは、あまり良くないそうです。
[1]によると、クラスが仮想関数を持つ場合、そのオブジェクトのサイズが大きくなります(50-100 %増)。
そのため、理由もなくデストラクタを仮想にするのは、全てのデストラクタを非仮想にするのと同様に誤りになるそうです。
サンプルコード
以下に、勉強で使用したコードを示します。
#include <iostream>
class BaseA {
public:
BaseA() { std::cout << "BaseA : コンストラクタ" << std::endl; }
virtual ~BaseA() { std::cout << "BaseA : デストラクタ" << std::endl; }
void print() { std::cout << "BaseA : print()" << std::endl; }
};
class ChildA : public BaseA {
public:
ChildA() { std::cout << "ChildA : コンストラクタ" << std::endl; }
~ChildA() { std::cout << "ChildA : デストラクタ" << std::endl; }
void print() { std::cout << "ChildA : print()" << std::endl; }
};
int main() {
/*
BaseA* a = new ChildA;
delete a;
*/
BaseA *a1 = new BaseA();
BaseA *a2 = new ChildA();
delete a1;
delete a2;
}
実行結果
BaseA : コンストラクタ
BaseA : コンストラクタ
ChildA : コンストラクタ
BaseA : デストラクタ
ChildA : デストラクタ
BaseA : デストラクタ
まとめ
今回は、基底クラスと派生クラスを宣言し、基底クラスのポインタ型で派生クラスのオブジェクトを動的に生成できることを確認しました。
そして、オブジェクトの派生クラス部分が動的に破棄されないといった問題を避けるために、仮想デストラクタについて学びました。
また、デストラクタを仮想にするかの判断について学びました。
- ポリモーフィズムのための基底クラスには仮想デストラクタを宣言する。
- 特に、クラスが仮想関数を持つ場合、仮想デストラクタを持たせる。
- 基底クラスとして設計されていないクラス、あるいは、基底クラスとして設計されていても、ポリモーフィズム的な使われ方をされていないクラスには、デストラクタを宣言すべきでない。
メモ
ポリモーフィズム
ポリモーフィズムは、は、日本語で「多態性」「多様性」「多相性」という意味を表します。
プログラムにおけるポリモーフィズム(多様性)は、同じメゾット(メゾット名)の呼び出しに対して、異なるオブジェクトが異なる処理をすることをいいます。
つまり、オブジェクトなどのデータ型に関する操作(インターフェース)が統一されていることです。
参考文献
[1] https://www.amazon.co.jp/gp/product/4621066099/ref=dbs_a_def_rwt_hsch_vapi_taft_p1_i0