#pragma twice

KAB-studio > プログラミング > #pragma twice > 382 Version 17.27 スマートポインタの自動解放、の続き

#pragma twice 382 Version 17.27 スマートポインタの自動解放、の続き

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

 Version 17.27
スマートポインタの自動解放、の続き

「では前回の続きから。まずこの行の話でした」

    cSmartPointer = GetPrinterInstance( DLG_PRINTER );

『右側の GetPrinterInstance() 関数もスマートポインタを返すんだよね』
「そう、だからこの関数を呼び出している部分が戻り値に置き換わります」

    CDebugPrinter クラスの変数のアドレスを持つ
          ↓             ↓CDlgPrinter クラスの変数のアドレスを持つ
    cSmartPointer = cSmartPointer;
         (左)            (右)

『でも、左右の持ってるアドレスが違う、と』
「このとき、 = 演算子のオーバーロードメンバ関数が呼び出されます」

    // = 演算子をオーバーロードします。
    CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )

「この中でアドレスが異なる場合には Release() メンバ関数を呼び出すよ
うにしてあります。で、 Release() メンバ関数は、参照カウンターが0に
なったら持っている変数を delete します」

                                  ┌ CDebugPrinter クラスの変数 ─┐
                     →           │Output()                      │
      両方の変数が                └───────────────┘
      deleteされます               ┌ int 型変数 ┐      ↑
                     →      →→→│ 0          │      ↑
                            ↑     └──────┘      ↑
┌ cSmartPointer(左)──┐  ↑                           ↑
│m_piRefCounter →→→→→→                            ↑
│m_pcPrinter →→→→→→→→→→→→→→→→→→→→→→
└───────────┘ 

『つまりこれが、〈スマートポインタが自動的に delete してくれた〉って
ことなんだよね』
「そういうこと。スマートポインタの最大のメリットは〈アドレスを誰も
持っていなかったら delete する〉っていう機能を持つこと。そうすること
で、適切なタイミングで delete されるわけです」
『まだ使っている時に delete することもないし、 delete し忘れて
メモリリークすることもない。必要なくなったらすぐに delete する、って
わけね』
「そういうこと。スマートポインタを使用することで〈使わなくなったら 
delete する〉っていう処理が自動的にできるわけです」
『なるほど、確かに便利……』
「さて、これで cSmartPointer 変数(左)が持つふたつのメンバ変数が
空きました。ここに、 cSmartPointer 変数(右)のアドレスをコピーします」

    // = 演算子をオーバーロードします。
    CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )
    {
// (略)
        // コピーします。
        m_pcPrinter = p_rcSmartPointer.m_pcPrinter;
        m_piRefCounter = p_rcSmartPointer.m_piRefCounter;
        // 参照カウンターを増やします。
        AddRef();

「アドレスをコピーして AddRef() メンバ関数を呼び出すことで、以下のよ
うにふたつのスマートポインタからひとつの〈 CDlgPrinter クラスの変数〉
のアドレスを持つことになるわけです」

┌ cSmartPointer(左)──┐ 
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→                         ↓
└───────────┘             ↓                        ↓
  ↑1つめ                              ↓                        ↓
┌ cSmartPointer(右)──┐      ┌ CDlgPrinter クラスの変数 ─┐ ↓
│m_pcPrinter →→→→→→→→→│Output()                    │ ↓
│m_piRefCounter →→→→→→   └──────────────┘ ↓
└───────────┘  ↓   ┌ int 型変数 ┐                ↓
  ↑2つめ                    →→│ 2←増えた  │←←←←←←←←
                                 └──────┘

『前回の最初と同じ状態、ってことね』
「そういうこと。この時点で、左右同じアドレスを持つわけです」

          ↓←←←←←←←↓CDlgPrinter クラスの変数のアドレスを持つ
    cSmartPointer = cSmartPointer;
         (左)            (右)

『左の方のが削除されて、右のに置き換わったわけね……』
「で、同じように、 cSmartPointer(右) は関数の戻り値だから」
『なくなってデストラクタが呼び出されるわけね』
「そして Release() メンバ関数が呼び出されて参照カウンターが1減らされ
ます。だから図にするとこうなります」

┌ cSmartPointer(左)──┐ 
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→                         ↓
└───────────┘             ↓                        ↓
                                       ↓                        ↓
                                ┌ CDlgPrinter クラスの変数 ─┐ ↓
                                │Output()                    │ ↓
      (なくなった)            └──────────────┘ ↓
                                 ┌ int 型変数 ┐                ↓
                                 │ 1←減った  │←←←←←←←←
                                 └──────┘

「こうして、 cSmartPointer(左) が持つアドレスが〈 CDlgPrinter クラス
の変数〉のアドレスに置き換わりました」
『おお』
「なので、次の行で GetPointer() メンバ関数を呼び出すと、この
〈 CDlgPrinter クラスの変数〉のアドレスが返されます」

    // 出力します。
    cSmartPointer.GetPointer()->Output( "あいうえお\n" );

『前回と同じで、ここでポリモーフィズムが機能してるわけね』
「そういうこと。 GetPointer() メンバ関数の戻り値の型は CPrinter クラス
のポインタだけど、実際にはその派生クラスの CDlgPrinter クラスの
アドレスが返される、というわけです」
『で、 Output() メンバ関数は仮想関数でオーバーライドされてるからそっ
ちが呼び出される、と。なるほどねー』
「さて、最後の締め」
『あれ? これで終わりじゃないの?』
「まだ残ってるよ。今 cSmartPointer 変数が持っている、
〈 CDlgPrinter クラスの変数〉と参照カウンターの〈 int 型変数〉を
delete しないと」
『あ、そか、それしないとメモリリークしちゃうね。でも、さっきは 
= 演算子をオーバーロードしていて、それで = で代入したからそれが呼ば
れたけど、今度はどうするの?』
「今実行してる WinMain() 関数をもう一度見てみようか」

int WINAPI WinMain
    ( HINSTANCE p_hInstance
    , HINSTANCE p_hPrevInstance
    , LPSTR p_pchCmdLine
    , int p_iCmdShow
    )
{
    // 出力用に、 CSmartPointer クラスを受け取ります。
    // まずデバッグ用を受け取ります。
    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );
    // 出力します。
    cSmartPointer.GetPointer()->Output( "あいうえお\n" );

    // 次にダイアログ用を受け取ります。
    cSmartPointer = GetPrinterInstance( DLG_PRINTER );
    // 出力します。
    cSmartPointer.GetPointer()->Output( "あいうえお\n" );
    ←ここまで来た。
    return 0;
}

「これから WinMain() 関数から抜けます。すると cSmartPointer 変数は?」
『なくなる……そか、デストラクタが呼ばれる!!』
「そういうこと。クラス型変数がなくなるときにはデストラクタが呼び出さ
れます」
『ってことは、 GetPrinterInstance() 関数から抜けるときと同じで、
デストラクタが呼び出されて、さらに Release() メンバ関数が呼び出され
ると』

    // 参照カウンターを 1 減らし、ゼロなら解放します。
    void Release()
    {
        // 参照カウンターをひとつ減らします。
        --( *m_piRefCounter );
        // 参照カウンターがゼロになったら解放します。
        if( *m_piRefCounter == 0 )
        {
            delete m_pcPrinter;
            delete m_piRefCounter;
        }
    }

「このメンバ関数では、まず参照カウンターがひとつ減ります」

┌ cSmartPointer(左)──┐ 
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→                         ↓
└───────────┘             ↓                        ↓
                                       ↓                        ↓
                                ┌ CDlgPrinter クラスの変数 ─┐ ↓
                                │Output()                    │ ↓
                                └──────────────┘ ↓
                                 ┌ int 型変数 ┐                ↓
                                 │ 0←減った  │←←←←←←←←
                                 └──────┘

『参照カウンターが 0 になった!!』
「ということは、その下の if の中に入るので delete が実行されます」

            delete m_pcPrinter;
            delete m_piRefCounter;

『で、前回と同じように CDlgPrinter クラスの変数と int 型変数が解放
されるわけね』

┌ cSmartPointer(左)──┐ 
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→                         ↓
└───────────┘             ↓                        ↓
                                       ↓                        ↓
                                ┌ CDlgPrinter クラスの変数 ─┐ ↓
                        →      │Output()                    │ ↓
         両方の変数が           └──────────────┘ ↓
         deleteされます          ┌ int 型変数 ┐                ↓
                        →       │ 0          │←←←←←←←←
                                 └──────┘

「というわけで、 WinMain() 関数から抜けたときには、 new で作られた
ふたつの変数はちゃんと delete されて、メモリリークは起きない、という
わけです」
『すご……』

/*
    Preview Next Story!
*/
『もう頭がこんがらがっちゃう!』
「まぁ、これは作るのも使うのも難しいからね」
『……使うくらいならなんとかならない?』
「実は使う時も注意が必要なんです」
『ええー!?』
「というわけで次回」
< Version 17.28 スマートポインタのまとめ >
『につづく!』
「こういう奥の深さが面白くなってくると……」
『それマゾよ絶対!』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。