デストラクタの振る舞い
職場で
継承先でデストラクタを省略したり、
virtualとつけなかったらどうなるの?
って言われたので、
「基底クラスに従うんだぜふじこ!」(ふじこは長いので略)
とかいう話をした。
質問者が何を心配してたかっていうと、
基底Aを継承したB、
Bを継承したCがあるとき、
Aはvirtualなデストラクタを持っているが、
Bにはデストラクタがなく、
Cにはvirtualを省略したデストラクタがある。
この場合、
A* ac = new C(); delete ac; B* bc = new C(); delete bc;
はCのデストラクタを正しく呼び出すのか?
ということだ。
呼び出されなかったらえらいことだろふじこ!!
Bには暗黙のデストラクタがあるんだふじこ!!
Cで省略してもAの状態を引き継ぐんだぜふじこ!!
だけど、省略すると読み手が混乱するから省略しないようにしなさいよねふじこ!!
規格で決まってるから、サンプルソース書いてみ?
といったら、すぐテストしてたので良いことだ。
このあたりの知識を持たずにC++を書くのはあぶねー、
という感じなんだが、多分、半分以上わかってないんだよな……。(勿論、俺もわかってません
これでAにvirtualがついてなかったら大惨事になるわけだが、
なぜそうしなければならないのか?
を知らずに記述してしまうことも恐ろしい。
ちょっとぐぐってたら、
Q7: でも何でデストラクタにvirtualをつけると大丈夫なの?
FAQ形式でデストラクタにvirtualを付ける理由をまとめてみた - かせいさんとこ
virtual付きのメソッドを持つと、コンパイラからこのクラスは継承されると認識されて、実行時型情報を持つようになる
実行時型情報があると、deleteの時に自分が何でnewされたか参照できるので、正しいデストラクタが呼ばれるようになる
いや、これは間違いだよね?
仮想関数がRTTIの機能であるかのように書かれているけれど、
そんなわけがなくて、
要するに仮想関数テーブルがあるか否か、という話な訳で、
インスタンスの先頭ないしは基底クラスの次くらいにある、(実装は規格で決まっていない
vptr(仮想関数テーブルを指すポインタ)の先にある、
vtbl(仮想関数テーブル)がtype infoを指す情報を持つか、
隣接して持っているとは考えられるが、
vtbl自体の機能は実行時型情報とは何の関係もない、
試しに、vptrを書き換えれば、違うクラスのデストラクタを呼び出すこともできるし、
地獄を見ることもできる。
てけとうなサンプル。
RTTIなくても勿論動く。
class A { public: A() : value_(999) { printf("A::constructor\n"); } virtual ~A() { printf("A::destructor\n"); } private: int value_; }; class B : public A { }; class C : public B { public: ~C(){ printf("C::destructor\n"); } }; class D : public B { }; int main() { A* ac = new C(); delete ac; B* b = new B(); delete b; B* bc = new C(); B* bd = new D(); // 悪魔の所行 void* bd_p = static_cast<void*>(bd); unsigned int* dest_p = static_cast<unsigned int*>(bd_p); void* bc_p = static_cast<void*>(bc); unsigned int* src_p = static_cast<unsigned int*>(bc_p); *dest_p = *src_p; delete bc; delete bd; return 0; }
悪魔の所行を行った結果、delete bd;は結果として、
Dではなく、
Cのデストラクタを実行してしまう。
まぁ、こういうことをしてしまうと、
デストラクタだけじゃなくて全ての仮想関数に関してCのものが呼ばれるようになる。
意図していなくてもmemcpyとかやっちゃう人がやりがちなので注意。
悪魔の所行は所詮悪魔の所行なので、移植性もないし動作する保証もないので注意。
オブジェクトのレイアウトは保証されないしなー。
一応gcc-4.0.1で確認。