基本クラスの継承を考えたデストラクタの属性設定
概要
この記事は間違った情報が多分に含まれるため
と
の翻訳記事に書き換える予定です。
以下元の記事。
実行時の型とポインタの型が違う場合、ただnewするだけでは実行時の型情報がロストするので、ちゃんとtype erasure
するかvirtualを付けましょう。
型情報がロストして困る例
#include <iostream> struct Foo{ ~Foo(){ std::cout << "Foo" << std::endl; } }; struct Bar: public Foo{ ~Bar(){ std::cout << "Bar" << std::endl; } }; int main(){ Foo *bar = new Bar(); delete bar; }
このコードではBarのデストラクタが実行されません。何故ならBarの型情報をどこにも保持していないからです。
virtualで解決する
struct Foo{ virtual ~Foo(){ std::cout << "Foo" << std::endl; } };
そこでデストラクタにvirtualを付けてやるとBarの型情報が(正確にはオーバーライドされた関数へのポインタ)が保持されるのでBarのデストラクタが実行されるようになります。
type erasureで解決する
int main(){ std::shared_ptr<Foo> bar(new Bar); }
std::shared_ptr
はtype erasure
というテクニックを使い型を内部で保持します。なのでBarのデストラクタにvirtualが付いていなくてもBarのデストラクタが実行されます。
どっちを使うべきか
Foo
がインターフェイスの場合継承される意図を明確にする為にvirtualを付けるのが良いと思います。反対にFoo
がただの一般クラスで同じ機能を流用したくて継承する場合はvirtualを付ける事を推奨しません。こういう場合は使いたい機能をインターフェイスに分離すべきです。
インターフェイスが何らかの事情で用意できない場合ですらstd::shared_ptr
で解放するのは意図が明白にならないのでオススメしません。継承先解放用のクラスを作成するのが本来なら良いのですが、このような複雑なテンプレートはしっかりとテストできる環境でのみ作成すべきです。なので、妥協案として継承先解放用のクラスをshared_ptrのエイリアスとして定義すると良いと思います。
余談
std::unique_ptr
は型情報を内部で保存していません。なので、継承先のデストラクタは実行されません。