きままにブログ

プログラミングを主とした私のメモ帳です。寂しいのでコメントください笑

仮想デストラクタ

ポインタをdeleteするときに、基底クラスのポインタをdeleteしようとすると非virtualなデストラクタの場合、継承先クラスのデストラクタが実行されないというよくあるお話です。

別にデストラクタに限らず、一般的なvirtual関数でもおなじ話ですが、特に明示的に呼び出さなかったり自動的に定義されたりするデストラクタでは注意が必要です。

#include <iostream>

using namespace std;

struct Base {
	Base() {
		cout << "Base::Base()" << endl;
	}
	~Base() {
		cout << "Base::~Base()" << endl;
	}
};

struct Derived : public Base {
	Derived() {
		cout << "Derived::Derived()" << endl;
	}
	~Derived() {
		cout << "Derived::~Derived()" << endl;
	}
};

int main() {
	Base* b = new Derived;
	delete b;

	cin.get();
}

実行結果は、

Base::Base()
Derived::Derived()
Base::~Base()

となります。virtual指定した関数は、元の型のオーバーライドされた方が実行されます。virtualでない関数は、呼び出すオペランドの型のものが実行されます。

なお、派生クラスのオーバーライドした関数もvirtual適用されます。派生先ではvirtualをつけてもつけなくても変わらないと言うことです。

ちなみにデストラクタに限ったことではありませんが、以下のように仮想関数を含むクラス、即ちプリモーフィックなクラスのインスタンスを、非ポリモーフィックな基底クラスに入れて実行できません。コンパイルエラーにはなりませんがセグフォを起こす可能性があります。

これは仮想基底クラス(virtual継承)の際も同じで、vtable分だけずれるのでセグフォが発生してしまいます。

struct Base {
	Base() {
		cout << "Base::Base()" << endl;
	}
	~Base() {
		cout << "Base::~Base()" << endl;
	}
};

struct Derived : public Base {
	Derived() {
		cout << "Derived::Derived()" << endl;
	}
	virtual ~Derived() {
		cout << "Derived::~Derived()" << endl;
	}
};

int main() {
	Base* b = new Derived;
	delete b; // だめ!

	cin.get();
}

継承を使う理由は、(1)機能継承 (2)インターフェイス (3)抽象型のいずれかでしょうから、このようなことは普通起こりません。機能継承であれば、基底クラスにキャストすることはありませんし、インターフェイスや抽象型であればそもそもvirtualだからです。

そもそもポインタを使わなければdeleteを使う必要がないので、これらの問題は起きません。

Derived d;
Base& b = d; // 参照

virtualを付けない場合

ところでvirtualを付けない場合はどういう場合でしょう? そもそもnew/deleteをできなくしておけばこのような問題は発生しませんから、そういう場合は付けなくても大丈夫でしょう。

機能実装等private/protected継承をする場合も要らないでしょう。また、継承される予定がないfinal指定されたクラスも必要ないでしょう。

要はポリモーフィズムをする予定がある場合だけ付ければいい(つけなければならない)ということでした。