Version 17.22
delete の方法
「前回はポリモーフィズムを new と delete で行う方法を説明しました」
『でも delete のタイミングとか方法の管理が難しー! あんな面倒なこと
しなきゃいけないの? ってゆーかなんで new しなきゃいけないんだっけ』
「一番の問題は、 Version 17.12 ( No.367 ) 等で説明したとおり、
ポリモーフィズムはポインタでないと使えないから」
『あーそっか、アップキャストしても型が変わらないようにするにはアドレス
渡すしかないんだもんね』
「そういうこと。つまり、ポリモーフィズムを使うためには」
・変数のアドレスを使用する
「ことが必要です」
『でも』
・関数から抜けるとローカル変数がなくなる
→戻り値でローカル変数のアドレスを返せない
→ new で変数を作るしかない
『ってことになって、だから面倒なことになるわけね』
「そういうこと。でも、そうなるといくつか問題点が発生します」
・ポインタをいつ解放するか
・使用中に解放してはいけない(存在しない変数を使ってしまう)
・解放し忘れてはいけない(メモリリークになる)
・解放前にアドレスを上書きしてはいけない(同上)
・delete で解放していいのか
・確保方法がわからないと解放できない
( new で確保したのか、それとも普通の変数なのか
アドレスからはわからない)
「こういった問題をある程度回避する方法があります」
『なにぃ! そんな方法があるんなら早く教えてよ』
「ただ、ちょっと上級者向けなのと、結構面倒だからその辺は憶えておい
て」
『う、つまり理解せず使うなってことね』
「そゆこと。まず比較的簡単な」
・delete で解放していいのか
「から解決します。これには方法がふたつあります」
・delete をオーバーロードする
・解放専用のメンバ関数を作る
「まずは delete のオーバーロードから」
・delete をオーバーロードする
「まず、 Version 11.13 ( No.213 ) を読み返してください。こんな箇所が
あったと思います」
void* AFX_CDECL operator new( /* 略 */ )
{
return ::operator new( /* 略 */ );
}
『うぉ、 operator って、演算子のオーバーロードのやつじゃん!』
「そう、 Version 16.15 ( No.342 ) とかで説明したね。実は、 new と
delete は演算子のひとつ、ということになってるんです」
『へー、全然演算子に見えないけど』
「ちなみに sizeof も演算子。このみっつは記号じゃない演算子ってこと」
『なんか不思議……』
「で、この new や delete も演算子だから、オーバーロードできるんです。
というわけで、 delete 演算子をメンバ関数でオーバーロードしてみます」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// delete 演算子で解放できないクラス。
class CUnDeletable
{
public:
// delete されたときに呼び出されるメンバ関数です。
// delete 演算子をオーバーロードしています。
void operator delete( void *p_pvThis )
{
OutputDebugString( "delete されました。\n" );
}
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CUnDeletable クラスの変数を作ります。
CUnDeletable cUnDeletable;
// この変数のアドレスをポインタに格納します。
CUnDeletable *pcUnDeletable = &cUnDeletable;
// それを delete で解放します。
delete pcUnDeletable;
// delete されました。
return 0;
}
『ぎゃーっ!! 何これ気持ち悪ッ!!』
「普通の変数を delete で解放してるように見えるからね……まず、
delete 演算子をメンバ関数でオーバーロードする場合、こういう引数と
戻り値になります」
void operator delete( void *p_pvThis )
↑delete に渡されたアドレス。
「 p_pvThis 引数には delete に渡したアドレス、つまり」
delete pcUnDeletable; ←このアドレスと同じ。
「この pcUnDeletable に入っているアドレスが渡されます」
『このアドレスでなにするの?』
「普通は、ここでメモリの解放を行います」
『そっか、それが delete だもんね』
「本来、 new と delete のオーバーロードは、デフォルトの方法じゃない
特殊な方法でメモリを確保したい場合に使います」
『 Version 11.13 ( No.213 ) のデバッグモードの時みたいにね』
「そゆこと。だから、本来なら new もオーバーロードして対にして確保と
解放を行うわけです。でも、今回は delete で何もしない、っていう形に
しました」
『だから、普通の変数を delete しても大丈夫なわけね』
「逆に言うと」
// CUnDeletable クラスの変数を new で作成します。
CUnDeletable *pcUnDeletable = new CUnDeletable;
// それを delete で解放します。
delete pcUnDeletable;
// delete されました。
↑実際には解放されてないのでメモリリークします。
「とかするとメモリリークします」
『……えっと、これって何に使うんだっけ?』
「前々回の static 変数を使った場合の話」
『あー! そか、 static 変数を使った時は delete しちゃいけない、それ
を防ぐのがこれってわけね』
「そういうこと。これなら static 変数や普通の変数をうっかり delete し
ても大丈夫ってことだね」
『それはそうだけど……』
「あともうひとつ、似た方法に」
・解放専用のメンバ関数を作る
「というものがあります。使用例はこんな感じ」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// Delete() メンバ関数で解放するクラス。
class CDeletable
{
public:
// 自分を解放するメンバ関数です。
virtual void Delete()
{
OutputDebugString( "delete します。\n" );
// 自分自身を delete します。
delete this;
}
};
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// CDeletable クラスの変数を new で作成します。
CDeletable *pcDeletable = new CDeletable;
// それを Delete() メンバ関数で解放します。
pcDeletable->Delete();
// delete します。
return 0;
}
「この CDeletable クラスは、 delete じゃなくメンバ関数で解放するよう
になっています」
『ぎゃーっ!! メンバ関数の中で delete 使ってる、しかも this に!』
virtual void Delete()
{
OutputDebugString( "delete します。\n" );
// 自分自身を delete します。
delete this; ← pcDeletable の中のアドレスと同じ。
}
「実はこんなふうに、メンバ関数の中で delete を呼び出すことができるん
です」
『これも気持ち悪い……』
「この方法のメリットは、ポリモーフィズムできること。実は、さっきの
delete 演算子は static メンバ関数という扱いなのでポリモーフィズム
できないんです」
『げ、そうなんだ』
「でもこっちの方法なら普通のメンバ関数だから」
『 virtual 付けて仮想関数にできるからオーバーライドできる』
「そうすることで、クラスによって解放方法を変えたりできるんです。クラス
によって解放方法を変えたい場合にはこっちの方が便利かな」
『でも間違えて delete しちゃいそう……』
/*
Preview Next Story!
*/
『な、なんか今回は異次元の世界でしたよ?』
「こういうトリッキーなプログラム、いっぱいあるよ」
『やっぱりトリッキーなんだよねやっぱり』
「でも C++ じゃ普通かも」
『なんですとー!?』
「というわけで次回」
< Version 17.23 スマートポインタ >
『につづく!』
「スマートポインタもとっても面白いよ?」
『なんだかその笑顔がハンザイシャに見えますよ?』