#7 ポリモーフィズムのための基底クラスには仮想デストラクタを宣言する.
Effective C++ 2章7項の内容です.
1.仮想関数
そもそも仮想関数とは何か?という話ですが,クラスのメンバ関数として宣言するときに,関数の前にvirtualをつけてあげれば仮想関数になります.で,なんでこんなことするか?ですが,下記の状況で使いたくなる時があります.
1.基底クラスに定義したメソッドを派生クラスでオーバーライドしている.
2.派生クラスを基底クラスのポインタとして扱っている.
3.基底クラスのポインタとして扱っていながらも,実際のオブジェクト(派生クラス)の振る舞いをしてほしい.(ポリモーフィズム)
1−1.virtualなしで定義した場合.
クラスの定義として,下記の2つがあるとします.
class HumanWoVirtual { public: ~HumanWoVirtual() { std::cout << "~HumanWoVirtual()" << std::endl; }; void say() { std::cout << "I am HumanWoVirtual!" << std::endl; }; }; class ManWoVirtual : public HumanWoVirtual { public: ~ManWoVirtual() { std::cout << "~ManWoVirtual()" << std::endl; }; void say() { std::cout << "I am ManWoVirtual!" << std::endl; }; };
ここで,基底クラス,派生クラスの両方にsay()という関数が定義されていますが,virtualがついていません.そのため,ポリモーフィズムを期待して下記のようなコードを書いても,意図した挙動になりません.
// ポインタとしてオブジェクトを扱う場合,振る舞いが異なる. // virtualとして関数が定義されている場合,ポインタが基底クラスの型になっていても,実体の関数が呼ばれる. { HumanWoVirtual *manWoVirtual = new ManWoVirtual; std::cout << std::endl; std::cout << "manWoVirtual->say()" << std::endl; manWoVirtual->say(); delete manWoVirtual; } { Human *man = new Man(); std::cout << std::endl; std::cout << "man->say()" << std::endl; man->say(); delete man; }
実行結果は,下記のようになります.見てわかる通り,アップキャストした側に定義されているsayが実行されています.これが望む挙動ならいいのかもしれませんが,「Humanの型として表現されていても,中身は実際にはManだから,Manのsayが呼ばれてほしい!」という場合には,say()の関数の頭にvirtualをつけてあげれば望む挙動に変わります.
manWoVirtual->say() I am HumanWoVirtual! <- ポインタ先に格納されている実体は ManWoVirtual であるにもかかわらず,HumanWoVirtual.say() が呼ばれている. ~HumanWoVirtual() <- ポインタ先に格納されている実体は ManWoVirtual であるにもかかわらず,~HumanWoVirtual() が呼ばれている.
1−2.virtualありで定義した場合.
クラスの定義を少しだけ変えました.
class Human { public: virtual ~Human() { std::cout << "~Human()" << std::endl; }; virtual void say() { std::cout << "I am Human!" << std::endl; }; }; class Man : public Human { public: virtual ~Man() { std::cout << "~Man()" << std::endl; }; virtual void say() { std::cout << "I am Man!" << std::endl; }; };
すると,実行結果が下記のように変わりました.ここで,一点注目するところとしては,static_castで実体をアップキャストした場合,結局コピーコンストラクタが呼ばれて,アップキャスト側のオブジェクトが実際に生成されます.そのため,”static_cast
man->say() I am Man! <- 基底クラスのポインタで呼ばれても,きちんと派生クラスの say() が呼ばれている. ~Man() <- 基底クラスのポインタで呼ばれても,きちんと派生クラスのデストラクタが呼ばれている. ~Human() man.say() I am Human! <- static_castすると,アップキャスト側のオブジェクトが実際に生成されるので,Human.say() が呼ばれる. ~Human() <- static_castで生成されたコピーに対するデストラクタのコール. ~Man() ~Human()