読者です 読者をやめる 読者になる 読者になる

ぬうぱんの備忘録

技術系のメモとかいろいろ

作った曲一覧はこちら

継承とか仮想デストラクタとか

C++ Tips

この記事は

    • 継承する時は仮想デストラクタ書けって言うけど実際の動作よくわかってないから検証してみた
    • private継承するとアップキャストできないのってなんでだろう?

みたいなことを調べました。

検証した環境

gcc バージョン 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)

仮想デストラクタの動作

Effective C++読んで``とりあえず継承する可能性のあるクラスには仮想デストラクタを書いておこう!''って思ったのは覚えてるんだけど、実際どんな感じの動作をしてどういう問題を引き起こすのか具体的なことが全然わからないので試してみた。

#include <iostream>
#include <string>

using namespace std;

class B1{
public:
	virtual ~B1(){
		cerr << "~B1()" << endl;
	}
};

class B2{
public:
	~B2(){
		cerr << "~B2()" << endl;
	}
};

class D : public B1, public B2{
public:
	~D(){
		cerr << "~D()" << endl;
	}
};

int main(int argc, char *argv[]){
	cerr << "delete as D" << endl;
	D* pd = new D();
	delete pd;

	cerr << "delete as B1" << endl;
	B1* pb1 = new D();
	delete pb1;

	cerr << "delete as B2" << endl;
	B2* pb2 = new D();
	delete pb2;

	return 0;
}
delete as D
~D()
~B2()
~B1()
delete as B1
~D()
~B2()
~B1()
delete as B2
~B2()

結果は

    • クラスDとしてdeleteした場合:適切にデストラクトされている
    • クラスB1としてdeleteした場合:適切にデストラクトされている
    • クラスB2としてdeleteした場合:B1とDのデストラクタが呼び出されていない

この結果からわかることは

    • deleteする時の型:静的な型
    • delete対象の実際の型:動的な型
    • 静的な型と動的な型が一致しない時
    • 静的な型で仮想デストラクタが宣言されてないと
    • 適切にデストラクタが呼び出されない

つまり

    • 仮想デストラクタの存在しないクラスにアップキャストしてdeleteするな
    • そもそも仮想デストラクタの存在しないクラスは継承するな
    • そもそもdeleteされる可能性のあるクラスは仮想デストラクタを書け

C++の規格的には仮想デストラクタの存在しないクラスをデストラクトすることは未定義の動作という話もあるので、どちらにしても仮想デストラクタは書くべきです。

private継承とアップキャスト

話は変わって、クラスDでB2をprivate継承するとDからB2へのアップキャストはできなくなります。なんでダメなの? って話は後回し。

class D : public B1, private B2{
public:
	~D(){
		cerr << "~D()" << endl;
	}
};

B2* pb2 = new D(); //<-エラー
エラー: ‘B2’ is an inaccessible base of ‘D’

どうしても継承したい時のprivate継承

この節は確固たる(書籍とかの)ソースがあるわけじゃないので嘘を言ってるかもしれません!

    • ``class D''と``class B''がprivate/protected継承の関係にある時
    • reinterpret_castでも使わないと``D*''を``B*''にキャストすることは出来ない

ということはつまり

    • (基本的に)class Dをclass Bとしてdeleteすることは出来ない

ということでもあります。ということは

    • 仮想デストラクタが存在しないクラスをどうしても継承したい
    • でもその基底クラスでdeleteされるとすごい困る
    • じゃあprivate/protected継承してdeleteできなくすればいいじゃない(!)

っていうことになりません?
本当に最終手段な感じなので他にやりようがないか、本当に継承しないといけないのか十分検討すべきです。
じっさい、private継承すべきケースはboost::noncopyableなんかを継承する時とかでしょうか?

どうしてprivate継承するとアップキャスト出来ないのか

この節も確固たる(書籍とかの)ソースがあるわけじゃないので嘘を言ってるかもしれません!
たとえばこんなケース

class B{
public:
	int value;
};

class D : private B{
};

int main(int argc, char *argv[]){
	B* pb = new D();	//これがOKということになってしまうと・・・
	pb->value = 0;		//privateなはずのvalueにアクセスできることになってしまう!
}

``B*''経由でメンバ変数valueにアクセスしようとすると、まるでメンバ変数valueがpublicであるかのように見えますが、実体はDとして生成されているのでメンバ変数valueは本来はprivateです。こういう状況を起こさないためにprivate継承のアップキャストは禁止されてるんですかね?
よく考えるとprivate継承のアップキャストはアクセス指定子を緩くするような行為なので出来ないのも当然といえば当然・・・?

感想

ちゃんとじぶんでたしかめることはとてもだいじなことだとおもいました(こなみかん)