非仮想protectedデストラクタについて

基底クラスに非仮想protectedデストラクタを適用すると、仮想関数テーブル使用しない分パフォーマンスの向上が望めるようだ。
ただし、基底クラスからdeleteできないという制約も存在する。これはコンパイル時にその結果がわかるということで、メリットにもデメリットにもなると思う。

#include <iostream>

class InterfaceBase
{
protected:
	~InterfaceBase(){}
};

class InterfaceConcreate : public InterfaceBase
{
};

int main()
{
	InterfaceConcreate* pInterface = new InterfaceConcreate();
	InterfaceBase* pBase = pInterface;
	delete pBase;         // ここでコンパイルエラー
	delete pInterface;
	return 0;
}

ただし、ここで基底クラスがメンバを持っていたり、非仮想メンバ関数や実装を伴うメンバ関数を持っていた場合、どのような振る舞いをすべきかがよくわからなかったので、実際に試してみた。

// base.h
#include <iostream>
#include <memory.h>

// 抽象として利用する場合
class InterfaceBase
{
protected:
	~InterfaceBase()
	{
		std::cout << "InterfaceBase Destruct." << std::endl;
	}

public:
	virtual void print() = 0;

};

// 仮想関数が実装を伴う場合
class VirtualFuncBase
{
protected:
	~VirtualFuncBase()
	{
		std::cout << "VirtualFuncBase Destruct." << std::endl;
	}

public:
	virtual void print()
	{
		std::cout << "VirtualFuncBase Print." << std::endl;
	}
};

// 非仮想関数を含む場合
class NonVirtualFuncBase
{
protected:
	~NonVirtualFuncBase()
	{
		std::cout << "NonVirtualFuncBase Destruct." << std::endl;
	}

public:
	void print()
	{
		std::cout << "NonVirtualFuncBase Print." << std::endl;
	}

};

// メンバを含む場合
class Member
{
public:
	Member()
	{
		memset(m_val, 1, sizeof(m_val));
	}
	~Member()
	{
		std::cout << "Member Destruct." << std::endl;
	}

	void print()
	{
		std::cout << "Member Print." << std::endl;
	}

private:
	char		m_val[32];
};

class HaveMemberBase
{
protected:
	~HaveMemberBase()
	{
		std::cout << "HaveMemberBase Destruct." << std::endl;
	}

private:
	Member	m_member;
};
// concreat.h
#include "base.h"

class InterfaceConcreate : public InterfaceBase
{
public:
	void print()
	{
		std::cout << "InterfaceConcerate Print." << std::endl;
	}
};

class VirtualFuncConcreate : public VirtualFuncBase
{
};

class NonVirtualFuncConcreate : public NonVirtualFuncBase
{
};

class HaveMemberConcreate : public HaveMemberBase
{
};
// main.cpp

#include "concreate.h"

int main()
{
	InterfaceConcreate* pInterface = new InterfaceConcreate();
	delete pInterface;

	VirtualFuncConcreate* pVF = new VirtualFuncConcreate();
	delete pVF;

	NonVirtualFuncConcreate* pNVF = new NonVirtualFuncConcreate();
	delete pNVF;

	HaveMemberConcreate* pMember = new HaveMemberConcreate();
	delete pMember;

	return 0;
}

ここで各オブジェクトを生成したときと破棄した時のメモリ状況を見てみる。

// InterfaceConcreate
・オブジェクト生成後
00 78 41 00 fd fd fd fd ab ab ab ab ab ab ab ab 00 00 00 00 00 00 00 00 01 01 08 00 ee 14 ee 00
78 01 3b 00 78 01 3b 00 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe

・オブジェクト破棄後
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe


// VirtualFuncConcreate
・オブジェクト生成後
2c 78 41 00 fd fd fd fd ab ab ab ab ab ab ab ab 00 00 00 00 00 00 00 00 01 01 08 00 ee 14 ee 00
78 01 3b 00 78 01 3b 00 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe

・オブジェクト破棄後
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe


// NonVirtualFuncConcreate
・オブジェクト生成後
cd fd fd fd fd ab ab ab ab ab ab ab ab fe ee fe 00 00 00 00 00 00 00 00 01 01 08 00 ee 14 ee 00
78 01 3b 00 78 01 3b 00 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe

・オブジェクト破棄後
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe


// HaveMemberConcreate
・オブジェクト生成後
01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
fd fd fd fd ab ab ab ab ab ab ab ab ee fe ee fe 00 00 00 00 00 00 00 00 fd 00 0c 00 ee 14 ee 00
78 01 3b 00 78 01 3b 00 ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe


・オブジェクト破棄後
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe
ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe ee fe

リークも起こらず正常に破棄できるようだ。

っていうか、そりゃそうか。
何のためのvirtualかっていうと、BaseからConcreateを逆引するための仮想関数テーブルだし、そもそもデストラクタの呼び出し順って派生→基底じゃんっていう。

と、いうことで、今度から基底クラスのポインタでdelete使われたくないとき、またはdeleteを使わなくていいときは非仮想protectedデストラクタつかってみよう。

そういえば、gccだと仮想関数あるけどnon-virtual destructorだぜ!ってwarningでるけどなんかいやだな。

(追記:2009/03/05)
基底クラスが仮想関数を持つ場合、デストラクタをvirtualにしようが、non-virtualにしようが、パフォーマンス的にはあまり変わりはないようだ。
なので、基底クラスが仮想関数を持たない場合のみ、非仮想protectedデストラクタを利用するとパフォーマンスの向上が望めるということになる。
但し、パフォーマンス計測についてはclock()をもちいて行ったので、あまり信頼できる数字ではない。とは言っても、仮想関数を持たない基底クラスを10000000回生成、破棄繰り返したら仮想関数を持つものより、安定して1秒以上早いのですが。仮想関数を持つクラスについては団子状態。
ここの認識が間違っていたら誰か教えてください。