Version 17.21
new / delete とポリモーフィズム
「前回は、インターフェイスを戻り値の型にする方法を説明しました」
『引数じゃなく戻り値でもできるわけね』
「戻り値にすることで、前回のプログラムを例にすると」
// まずデバッグ用を受け取ります。
pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
// 出力します。
pcPrinter->Output( "あいうえお\n" );
// ↑ CDebugPrinter::Output() が呼ばれます。
// 次にダイアログ用を受け取ります。
pcPrinter = GetPrinterInstance( DLG_PRINTER );
// 出力します。
pcPrinter->Output( "あいうえお\n" );
// ↑ CDlgPrinter::Output() が呼ばれます。
「と、同じ Output() メンバ関数を呼び出していますが、実際には
GetPrinterInstance() 関数で取得したクラスのメンバ関数を呼び出してい
ます」
『ポリモーフィズムしてるわけね』
「これは実は、引数でインターフェイスを使う時よりも応用範囲が広いんで
す。今回みたいに〈処理が似ているものを切り替えたい〉場合に使うことが
できます」
『処理が似ているもの?』
「たとえば」
・デバッグ出力
・ダイアログ出力
・ファイル出力
・ネットワーク出力
「みたいな出力系」
『考えてみたら出力って文字列渡すだけだから簡単にできるんだね』
「こういう場合、たとえば次のようなプログラムを組むこともできます」
// 現在のモードを取っておきます。
int iMode = DEBUG_PRINTER; // デバッグ出力。
// int iMode = DEBUG_PRINTER; // ダイアログ出力。
// ...
// エラーが発生したのでメッセージを出力します。
if( iMode == DEBUG_PRINTER )
{
// デバッグモードなのでデバッグ出力します。
OutputDebugString( "エラーです。" );
}
else
{
// ダイアログモードなのでダイアログ出力に出力します。
MessageBox( NULL, "エラーです。", "デバッグ", MB_OK );
}
『あー、 if で切り替えるわけね』
「でもこの方法だと、出力箇所ごとにこういうチェックが必要になります」
『でも、ポリモーフィズムを使えばそれが必要ない!』
「というわけ。ポリモーフィズムには、こんな感じに〈似た処理をまとめて
切り替える〉ことにも使えるわけです」
『なるほど、こういう使い方が分かってくると色々便利ねー』
「ただ、このように戻り値で返す方法には、大きな問題があります。今回の
例では以下のように変数を作成しました」
// 「出力」クラスを返す関数。
// 引数が DEBUG_PRINTER なら CDebugPrinter クラスのポインタを、
// DLG_PRINTER なら CDlgPrinter クラスのポインタを返します。
CPrinter *GetPrinterInstance( int p_iFlag )
{
// 両クラスの変数を static 変数として作っておきます。
static CDebugPrinter cDebugPrinter;
static CDlgPrinter cDlgPrinter;
// フラグによって出力を変更します。
if( p_iFlag == DEBUG_PRINTER )
{
return &cDebugPrinter;
}
// else
return &cDlgPrinter;
}
「このように、変数を static 変数として作成して、そのアドレスを返して
いました」
『そうしないと関数から抜けたときに消えちゃうもんね』
CPrinter *GetPrinterInstance( int p_iFlag )
{
↓ static 変数にしなかった場合……
CDlgPrinter cDlgPrinter;
return &cDlgPrinter; ←ここで cDlgPrinter 変数がなくなります。
↑ということは、返されたアドレスは変数ではない場所を指している
ので、無効なアドレスということになります。
}
「これを避けるために、 static 変数にしているわけです。 static 変数は
Version 6.02 ( No.102 ) で説明したとおり〈関数内で一度作られたらなく
ならない〉変数なので、アドレスが無効になることはありません」
CPrinter *GetPrinterInstance( int p_iFlag )
{
↓ static 変数にした場合……
static CDlgPrinter cDlgPrinter;
return &cDlgPrinter; ← cDlgPrinter 変数はなくなりません。
↑返されたアドレスはちゃんと cDlgPrinter 変数を指します。
}
『だから static にしてたわけだね。でもなんか不自然……』
「そうだね、一番の問題はもし CDlgPrinter クラスや CDlgPrinter クラス
にメンバ変数があった場合に共有されちゃうこと」
『げ、そうじゃん!』
pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
↑どれも同じ変数のアドレスを持っているわけです。
『同じ変数を使い回すわけだから、メンバ変数が作れない……ってことは、
GetPrinterInstance() 関数呼ぶたびに別の変数を作ってもらう必要があるっ
てことね』
「そう、その方法は?」
『 new !』
「そう! new を使えばいいわけです。まず、 GetPrinterInstance() 関数
ではこんな感じに new で変数を作って返します」
// 「出力」クラスを返す関数。
// 引数が DEBUG_PRINTER なら CDebugPrinter クラスのポインタを、
// DLG_PRINTER なら CDlgPrinter クラスのポインタを返します。
CPrinter *GetPrinterInstance( int p_iFlag )
{
// フラグによって出力を変更します。
if( p_iFlag == DEBUG_PRINTER )
{
return new CDebugPrinter;
}
// else
return new CDlgPrinter;
}
『 static 変数を使わないで、 new でその場で変数作っちゃうわけね』
「使う側は、使用後に必ず delete します」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// 出力用に、 CPrinter クラスのポインタを受け取ります。
CPrinter *pcPrinter;
// まずデバッグ用を受け取ります。
pcPrinter = GetPrinterInstance( DEBUG_PRINTER );
// 出力します。
pcPrinter->Output( "あいうえお\n" );
// 解放します。
delete pcPrinter;
// 次にダイアログ用を受け取ります。
pcPrinter = GetPrinterInstance( DLG_PRINTER );
// 出力します。
pcPrinter->Output( "あいうえお\n" );
// 解放します。
delete pcPrinter;
return 0;
}
『うん、これなら毎回作られるから大丈夫ね』
「 new と delete については Version 5.22 ( No.087 ) や
Version 11.12 ( No.212 ) を読み返してください」
『クラス使ってるから、 malloc() とかは使えないんだよね』
「そう、コンストラクタを呼び出してくれるのは new だけだから、ここで
は new を使う必要があります」
『うん。……でもさ、そんな難しくないじゃん。これでいいんじゃない?
もう new と delete は習ってるんだし』
「そんな簡単な問題じゃないよ。まず重要なのは」
・いつ解放するか
「っていうこと」
『使ったら delete すればいいんじゃないの?』
「すぐ delete するならそれでもいいんだけど、でもたとえば今回みたいに
デバッグ用のクラスの場合、毎回作るよりはある程度使い回した方がいいで
しょ」
『確かにそうね……引数で渡したりメンバ変数で持ったりする方が楽そう』
「そうなるといつ delete で解放すればいいのか、そのタイミングが難しく
なります」
『確かに』
「さらに、同じポインタでいろんな変数を参照する場合、たとえばこの例の
pcPrinter 変数みたいな場合は、切り替え前に delete し忘れないようにし
ないと、切り替え前の変数が delete で解放できなくなります」
『それってメモリリークって言うんだっけ』
「 Version 11.09 ( No.209 ) で説明したね。こういった問題に加えて」
・何で解放するのか
「を間違える可能性もあるから」
『それはないでしょ、クラスなら delete 一択じゃん』
「そう? たとえば GetPrinterInstance() 関数が、さっきみたいに
static 変数のアドレスを返していたら?」
『げっ、そっか、ポインタだから delete すればいいって問題じゃないんだ』
「といった問題があるわけです」
/*
Preview Next Story!
*/
『うーん、なんだか前に解決したと思った問題が……』
「というわけで次回も引き続き new と delete の話」
『げ、まだですか』
「当分この話が続くから」
『え〜?』
「というわけで次回」
< Version 17.22 delete の方法 >
『につづく!』
「ポインタ関係は避けられない問題だからね」
『避けられないの? 絶対?』
「うん、はっきり言ってずっと」
『えぇ〜!?』