#pragma twice

KAB-studio > プログラミング > #pragma twice > 380 Version 17.25 戻り値の一時変数

#pragma twice 380 Version 17.25 戻り値の一時変数

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

 Version 17.25
戻り値の一時変数

「前回はスマートポインタの概要を説明しました」
『まだ全然分からないけど』
「というわけで前回の続きから。 GetPrinterInstance() 関数の以下の箇所
で、 CSmartPointer クラスの変数が作られます」

        CSmartPointer cSmartPointer( new CDebugPrinter() );

「この cSmartPointer 変数は、コンストラクタでこんな感じに作られます」

┌ cSmartPointer───┐          ┌ CDebugPrinter クラスの変数 ─┐
│m_pcPrinter →→→→→→→→→→│Output()                      │
│m_piRefCounter →→→→→→     └───────────────┘
└──────────┘    ↓     ┌ int 型変数 ┐
                             →→→│ 1          │
                                   └──────┘
「 cSmartPointer 変数が【スマートポインタ】、右の CDebugPrinter クラス
が Version 17.20 ( No.375 ) から使用している【使用したいクラス】」
『この CDebugPrinter クラスがポリモーフィズムするんだよね』
「そういうこと。正確に言うと、 cSmartPointer 変数の中にある 
m_pcPrinter ポインタがポリモーフィズムする感じかな」
『で、最後の int 型変数が、参照カウンタっていうのなんだよね』
「そう、これから説明するけど、 CDebugPrinter クラスの変数のアドレス
は複数のスマートポインタが持つことになります」
『その数をここに取っておく、ってことなんだよね』
「そういうこと。実際にそうなる流れを見ていきます。先ほどの変数を作っ
ている箇所の次の行は、以下のようになっています」

        return cSmartPointer;

『さっきの変数を return で返してるわけね』
「この return で返す変数は、以下の GetPrinterInstance() 関数を呼び出
している箇所に置き換わります」

    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );

「つまり、これは以下のようになっている、ということです」

                           return cSmartPointer;
                                     ↓
    CSmartPointer cSmartPointer = cSmartPointer;
                     (左)              (右)

『関数が return で返した変数に置き換わる、ってわけね』
「ここのポイントは以下の2点」

・左の cSmartPointer 変数が、今、作られようとしている。
 →コンストラクタが呼び出される。
・ = の左と右が同じ型。
 →コピーコンストラクタが呼び出される。

『コピーコンストラクタ?』
「そう、 Version 16.12 ( No.339 ) で説明したように、 = 演算子でも
〈変数をを作るとき〉ならコンストラクタが呼び出されます。加えて、同じ 
CSmartPointer クラスだから」
『コピーコンストラクタが呼び出される、ってわけね』
「そこで CSmartPointer クラスのこのコピーコンストラクタが呼び出され
ます」

    // コピーコンストラクタ。
    CSmartPointer( const CSmartPointer &p_rcSmartPointer )
        : m_pcPrinter( p_rcSmartPointer.m_pcPrinter )
        , m_piRefCounter( p_rcSmartPointer.m_piRefCounter )
    {
        AddRef();
    }

『 m_pcPrinter と m_piRefCounter のメンバ変数をコピーしてるんだね』
「この時点で、図にすると以下のようになります。

┌ cSmartPointer(右)──┐        ┌ CDebugPrinter クラスの変数 ─┐
│m_pcPrinter →→→→→→→→→→│Output()                      │
│m_piRefCounter →→→→→→     └───────────────┘
└───────────┘  ↓     ┌ int 型変数 ┐      ↑
                             →→→│ 1          │      ↑
                            ↑     └──────┘      ↑
┌ cSmartPointer(左)──┐  ↑                           ↑
│m_piRefCounter →→→→→→                            ↑
│m_pcPrinter →→→→→→→→→→→→→→→→→→→→→→
└───────────┘ 

『あ、そっか。さっきの〈関数が戻り値に置き換わる〉のだと、』

    CSmartPointer cSmartPointer = cSmartPointer;
                     (左)              (右)

『左右の cSmartPointer って、両方のメンバ変数が同じ変数指してるんだ』
「そう、両方とも同じアドレスを持ってるからね。さらに、このあと
コピーコンストラクタは AddRef() メンバ関数を呼び出しています」

    // 参照カウンターを 1 増やします。
    void AddRef()
    {
        // 参照カウンターをひとつ増やします。
        ++( *m_piRefCounter );
    }

「この m_piRefCounter メンバ変数は、上の図の〈 int 型変数〉を指して
るから」
『ってことは、この 1 が増えるんだ』
「だからこうなります」

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

「前回説明したように、この int 型変数は参照カウンター、つまり
〈CDebugPrinter クラスの変数〉のアドレスを持っているスマートポインタ
の数を持っています」
『あ、ってことは、 cSmartPointer(左) が作られたから、 1 増やしたって
こと?』
「そういうこと。重要なことは〈コピーコンストラクタが呼び出される〉と
いうことは必ず〈元々 CSmartPointer クラスの変数があってそれが渡され
る〉ということ。だから、ポインタをコピーして、参照カウンターを増やす
んです」
『……絶対にそれ以外はない、ってこと?』
「そう、コピーコンストラクタは同クラスの変数が渡された時にしか呼ばれ
ない、っていうルールがあるからね。そのルールをふまえて、こういうふう
に作っているわけ」
『なんかパズルみたい……』
「それはあるかもね。さて、ここからまた複雑になります」
『げげ!!』
「さっきの、関数が戻り値に置き換わった部分を見てみます」

    CSmartPointer cSmartPointer = cSmartPointer;
                     (左)              (右)

「この右側の cSmartPointer 変数は、元々は GetPrinterInstance() 関数
の変数です」
『そだね、 return で返したのだから』
「で、関数の中で作った変数は……」
『関数から抜けるとなくなる!』
「そう、だから cSmartPointer (左)のコピーコンストラクタが呼び出され
たあと、 cSmartPointer (右)のデストラクタが呼び出されるんです。

            先にこちらのコピーコンストラクタが呼ばれる。
                       ↓    そのあとこちらのデストラクタが呼ばれる。
                       ↓               ↓
    CSmartPointer cSmartPointer = cSmartPointer;
                     (左)              (右)

『デストラクタって Version 11.11 ( No.211 ) でやった、変数がなくなる
時に呼び出されるメンバ関数だよね』
「そう、 CSmartPointer クラスだと以下のメンバ関数がそうです」

    // デストラクタ。
    ~CSmartPointer()
    {
        Release();
    }

『そうそう、クラス名に【~】を付けたのがそうなんだよね』
「このデストラクタが呼び出されると、その中で Release() メンバ関数を
呼び出します。このメンバ関数はこうなっています」

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

「このメンバ関数では、まず参照カウンターを減らします」

        --( *m_piRefCounter );

『お、 AddRef() メンバ関数と逆だ』
「こうすることで、 int 型変数が 1 減ります」

┌ cSmartPointer(右)──┐        ┌ CDebugPrinter クラスの変数 ─┐
│m_pcPrinter →→→→→→→→→→│Output()                      │
│m_piRefCounter →→→→→→     └───────────────┘
└───────────┘  ↓     ┌ int 型変数 ┐      ↑
   ↑1つめ(今なくなる)     →→→│ 1 ←減った │      ↑
   ↓2つめ。                ↑     └──────┘      ↑
┌ cSmartPointer(左)──┐  ↑                           ↑
│m_piRefCounter →→→→→→                            ↑
│m_pcPrinter →→→→→→→→→→→→→→→→→→→→→→
└───────────┘ 

「そのあと、この参照カウンターが 0 になったかチェックします」

        // 参照カウンターがゼロになったら解放します。
        if( *m_piRefCounter == 0 )

『でも 0 になってないよね』
「うん、だからこの段階では if の中には入りません。これはあとでまた説
明します」
『で、このメンバ関数は終わり、かな』
「そう、デストラクタからも抜けて cSmartPointer (右)はなくなります」

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

「ちゃんと参照カウンターの整合性が取れてることに注目してください」
『 CDebugPrinter クラスの変数を指してるスマートポインタが 1 つだけ、
だから int 型変数は 1 、おおホントだ!』
「というわけで次回に続く!」

/*
    Preview Next Story!
*/
『なんか本当にパズルみたい』
「 C++ っていうルールで遊ぶ感じかな」
『すんごく複雑なルールだけど』
「そのルールをちゃんと理解すれば……」
『楽しめるようになる?』
「というわけで次回」
< Version 17.26 スマートポインタの自動解放 >
『につづく!』
「ま、楽しくなってはまるとそれはそれで問題だけど」
『だめじゃん』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。