#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のトップページをご覧ください。