#pragma twice

KAB-studio > プログラミング > #pragma twice > 376 Version 17.21 new / delete とポリモーフィズム

#pragma twice 376 Version 17.21 new / delete とポリモーフィズム

前のページへ 表紙・目次へ 次のページへ

 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 の方法 >
『につづく!』
「ポインタ関係は避けられない問題だからね」
『避けられないの? 絶対?』
「うん、はっきり言ってずっと」
『えぇ〜!?』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。