Version 17.30
デストラクタを仮想関数にする
「前回は継承とコンストラクタの関係について説明しました」
『ただでさえ複雑なコンストラクタが、よけい複雑に……』
「で、今回はデストラクタについて」
『コンストラクタとデストラクタは対だもんね』
「でもコンストラクタほど難しくはないから。ただ押さえておかなきゃいけ
ないのが、継承されるクラスのデストラクタは必ず仮想関数にしなければい
けない、という点」
『え、そなの?』
「これまでは継承もデストラクタもあまり使わなかったから説明してこな
かったけど、継承をする場合、基本クラスのデストラクタは必ず仮想関数に
する必要があるんです」
『でもさ、仮想関数ってオーバーライドした方が呼び出されるようにする
仕組みだよね。でもさ、デストラクタって変数がなくなるときに自動的に
呼び出されるんだから、そんなこと気にしなくていいんじゃない?』
「そこが盲点なんだよね。まぁ、その辺の確認も含めて、デストラクタと
継承について見てみようか。まずは簡単な例から」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 基本クラス。
class CBase
{
public:
// デストラクタ(仮想関数ではありません)。
~CBase()
{
OutputDebugString( "CBase::~CBase()\n" );
}
};
// 派生クラス。
class CDerived : public CBase
{
public:
// デストラクタ(仮想関数ではありません)。
~CDerived()
{
OutputDebugString( "CDerived::~CDerived()\n" );
}
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を作ります。
CDerived cDerived;
return 0;
// 変数がなくなるので、デストラクタが呼び出されます。
// CDerived::~CDerived()
// CBase::~CBase()
}
「このプログラムでは、 CBase クラスが基本クラス、 CDerived クラスが
派生クラスです」
『両方ともデストラクタ持ってるね』
「ここで、 CDerived クラスの変数を WinMain() 関数内で作ります」
// CDerived クラスの変数を作ります。
CDerived cDerived;
「で、 WinMain() 関数から抜けるときにこの cDerived 変数がなくなりま
す。デストラクタは変数がなくなるときに自動的に呼び出されるメンバ関数
なので、このときデストラクタが呼び出されます」
return 0;
// 変数がなくなるので、デストラクタが呼び出されます。
// CDerived::~CDerived()
// CBase::~CBase()
『お、派生クラスと基本クラス両方とも呼ばれてる』
「デストラクタはコンストラクタと逆で、派生クラスが先、基本クラスが後
です」
『あ、それってコンストラクタのときと同じ理由だよね。基本クラスが先に
なくなっちゃったら派生クラスのデストラクタで基本クラス使えないもん』
「そういうこと! 基本クラスは派生クラスから使われる可能性がある、だ
から派生クラスより先に基本クラスがなくならないようになってるわけです」
『ちゃんとしてるのねー』
「さて、この例ではデストラクタを仮想関数にしていません。すると、実は
ポリモーフィズムを使った時に問題が発生するんです。たとえば以下のよう
に使用した場合」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を new で作ります。
CBase *pcBase = new CDerived();
// それを delete で解放します。
delete pcBase;
// 変数がなくなるので、デストラクタが呼び出されます。
// CBase::~CBase()
return 0;
}
「まず、派生クラスの CDerived クラスを new で作り、そのアドレスを
基本クラスの CBase クラスのポインタで受け取ります」
// CDerived クラスの変数を new で作ります。
CBase *pcBase = new CDerived();
『 Version 17.08 ( No.363 ) のアップキャスト使ってるんだね』
「これを、 delete で解放すると、派生クラスの CDerived クラスの
デストラクタが呼び出されないんです」
// それを delete で解放します。
delete pcBase;
// 変数がなくなるので、デストラクタが呼び出されます。
// CBase::~CBase()
『げ、ホントだ!! これってさっき言ってた、仮想関数にしてないからっ
てこと?』
「そういうこと。 Version 17.13 ( No.368 ) で説明したように、仮想関数
じゃないメンバ関数はポインタの型のメンバ関数が呼ばれるんです。これは
デストラクタも同じってこと」
『この例だと pcBase 変数の型、つまり CBase クラスのデストラクタしか
呼ばれないから、 CDerived クラスのデストラクタが呼び出されない、って
ことなのね』
「そこで、基本クラスのデストラクタを仮想関数にします」
// 基本クラス。
class CBase
{
public:
// デストラクタ(仮想関数です)。
virtual ~CBase()
{
OutputDebugString( "CBase::~CBase()\n" );
}
};
「こうして virtual をつけることで、基本クラスも派生クラスも、
デストラクタが仮想関数になります」
『そういえば基本クラスのメンバ関数を仮想関数にすれば、オーバーライド
したメンバ関数も仮想関数になるんだったね』
「こうすることで、ちゃんと派生クラスのデストラクタも呼び出されるよう
になります」
delete pcBase;
// 変数がなくなるので、デストラクタが呼び出されます。
// CDerived::~CDerived()
// CBase::~CBase()
『おお! ちゃんと呼び出されてる!!』
「このように、基本クラスのデストラクタを仮想関数にすることで、
派生クラスのデストラクタも呼び出されるようになるわけです」
『なるほどねー』
「で、はっきり言うと、実は〈継承するかもしれないクラスは必ず仮想関数
のデストラクタを持つ〉ようにしてください」
『継承するかもしれない……ってほとんどのクラスがそうじゃないの?』
「うん、だから全部のクラスでそうしてもいいかも」
『でもさ、デストラクタって後始末するメンバ関数じゃん。使うことあんま
りないんじゃないの?』
「実はそうじゃないんです。デストラクタは、作らなくても自動的に作られ
るんです。で、デストラクタは〈メンバ変数を削除する〉っていう大事な
機能を持ってるんです」
『メンバ変数を削除する……?』
「たとえば」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// ただのクラス。
class CTest
{
public:
// デストラクタ。
~CTest()
{
OutputDebugString( "CTest::~CTest()\n" );
}
};
// 基本クラス。
class CBase
{
public:
// デストラクタ(仮想関数ではありません)。
~CBase(){}
};
// 派生クラス。
class CDerived : public CBase
{
CTest m_cTest;
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDerived クラスの変数を new で作ります。
CBase *pcBase = new CDerived();
// それを delete で解放します。
delete pcBase;
// 変数がなくなるので、デストラクタが呼び出されます。
return 0;
}
「 CDerived クラスは CTest クラスのメンバ変数を持っています。この
CTest クラスはデストラクタを持っていて、削除されるときに出力をします」
『うん、だから CDerived クラスの変数が delete で解放されるときに一緒
になくなる……んだよね?』
「この例ではなくなりません」
『えええっ!? ……それって、さっき言ってた、基本クラスのデストラクタ
が仮想関数じゃないから、派生クラスのデストラクタが呼ばれない、だから
ってこと???』
「そういうこと。だから、 CBase クラスのデストラクタを……」
virtual ~CBase(){}
「って仮想関数にすれば」
// それを delete で解放します。
delete pcBase;
// 変数がなくなるので、デストラクタが呼び出されます。
// CTest::~CTest()
『あ、ちゃんと出力された』
「このように、派生クラスでデストラクタを作らなくても、基本クラスには
必ずデストラクタを持たせて、仮想関数にするようにしてください」
/*
Preview Next Story!
*/
『また見えないところで何かしているのが……』
「こういうのをひとつずつ憶えていくのが近道かも」
『はい先生! とても憶えきれません!』
「そろそろネタも尽きてくるから大丈夫……かな」
『ってことは次は……』
「というわけで次回」
< Version 17.31 動的型チェック >
『につづく!』
「うん、ちゃんと見える話……かな?」
『どっちなのよ!!』