FAQ形式でデストラクタにvirtualを付ける理由をまとめてみた
Q1: デストラクタにvirtualをつけろとよく言われるけど、なんで?
挙動が未定義のdeleteを呼び出す可能性があるから
Q2: 挙動が未定義だと駄目なの?
超駄目。何が起きても文句は言えない
Q3: どんな時に挙動が未定義のdeleteが呼び出されるの?
こんなとき
// NoVirtualBaseClass : デストラクタにvirtualを持たない // NoVirtualSuperClass : NoVirtualBaseClassを継承 NoVirtualBaseClass * ptr = new NoVirtualSuperClass(); // 挙動が未定義 delete ptr;
Q4: 何でそうなるの?
delete時点で、ptrは自分がNoVirtualBaseClassのインスタンスだと思ってるから
Q5: 何で?NoVirtualSuperClassでnewしたのに
C++のインスタンスは、標準では、自分の動的な型情報を持ってない
分かるのは、宣言時の型情報だけ
だから、deleteの時に本当はNoVirtualSuperClassのインスタンスなのに、NoVirtualBaseClassのデストラクタしか呼ばれない
この時の挙動が未定義
大抵は、NoVirtualSuperClassの差分だけメモリリークだろうけど、他のインスタンスがぶっ壊れたり、何が起きても文句は言えない
Q6: そうしない為にデストラクタにvirtualが必要なのね
そういうこと
Q7: でも何でデストラクタにvirtualをつけると大丈夫なの?
virtual付きのメソッドを持つと、コンパイラからこのクラスは継承されると認識されて、実行時型情報を持つようになる
実行時型情報があると、deleteの時に自分が何でnewされたか参照できるので、正しいデストラクタが呼ばれるようになる
Q8: だったら最初から全部のクラスに実行時型情報がついてりゃいいのに
2つの理由から無理
Q9: 低レベルプログラミングや、後方互換性の話だから、普通のPCだけで使う分には全部にvirtual付けても、問題ない?
わりとそう
組み込みみたいな環境でなければ、速度やサイズに有意な差は出ないみたい
→RTTI のコストを理解する
なので、全てのデストラクタに無条件にvirtualをつけると言うのも一つの答えだと思う
Q11: でも、全部にvirtual無いと、間違いで非仮装クラスを継承されたとき危なくない?
コンパイル時にエラーを出す方法があります
コンストラクタをprivateにしちゃえば、継承しても、コンパイル時に以下のエラーが出ます
error C2248: privateメンバにアクセスできません。
これは、継承したクラスのコンストラクタでは、暗黙で最初に基底クラスのコンストラクタを呼ばなきゃいけないけど、コンストラクタがprivateになると、それが出来なくなるから
Q12: これやると普通のnewもできなくね?
singleton、factoryパターンとかでポインタを渡す様にすればOK
それぞれ概要を書くけど、詳しくは解説書を参考に
singletonパターン
あるクラスのインスタンスは1つしか作らせないパターン
class Single { private : static Single * instance = 0x00; Single( void ){} // コンストラクタはprivate public : // ポインタを取得 static Single * getInstance( void ){ if( instance == 0x00 ){ instance = new Single(); } return instance; } };
factoryパターン
要求されると、インスタンスを生成するパターン
class SimpleFactory { private : SimpleFactory( void ){} // コンストラクタはprivate public : // ポインタを生成 static SimpleFactory * makeInstance( void ){ return new SimpleFactory(); } };
Q13: ポインタを渡すと、deleteが呼び出した人の責任になるから怖い
proxyパターンでポインタを実体に隠蔽して渡せばOK
// newとdeleteを隠蔽する事で、ポインタの存在を意識させない class ProxySimpleFactory { private : SimpleFactory * ptr; public : ProxySimpleFactory( void ){} ~ProxySimpleFactory( void ){ if( instance != 0x00 ){ delete instance; } } // 本当はインターフェースを作って // SimpleFactoryの持つメソッドを持って // 使う人からは、ポインタを隠蔽した方がベター SimpleFactory * getInstance( void ){ if( instance == 0x00 ){ instance = SimpleFactory::makeInstance(); } return ptr; } };
Q14: そこまでして継承を禁止するのは逆にオーバーヘッドでない?
俺もそう思う。
実際の所は非virtualにしておいて、規約で回避するのが一番だと思います。
二番手は、全部virtualにしちゃう。
実装時のメンバーのレベルで決めればよいと思う。
経緯
一ヶ月前からQ9以降がわかんなくて、こないだやっと分かったのでまとめました。
そんなかんじです。