#pragma twice

KAB-studio > プログラミング > #pragma twice > 379 Version 17.24 スマートポインタと参照カウンター

#pragma twice 379 Version 17.24 スマートポインタと参照カウンター

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

 Version 17.24
スマートポインタと参照カウンター

「今回は前回に引き続きスマートポインタについて説明します」
『この前はプログラムだけだったもんね』
「まず、スマートポインタの基本方針は以下の6つ」

・スマートポインタはクラス。
・中に、実際に使用するクラスのポインタを持つ。
・スマートポインタはポインタではなく普通の変数として使う。
・【参照カウンター】へのポインタをメンバ変数に持つ。
・複製ができたら参照カウンターを増やし、変数が消えたら減らす。
・参照カウンターがゼロになったときだけ解放する。

「これを踏まえて、スマートポインタについて見ていきます」
『はーい』
「まず、スマートポインタはクラスです」

・スマートポインタはクラス。

「なので、クラスとして作ります」

// スマートポインタクラス。
class CSmartPointer

「次に、対象のポインタをメンバ変数として持ちます」

・中に、実際に使用するクラスのポインタを持つ。

「これが以下の行です」

    // これが対象のポインタ。
    CPrinter *m_pcPrinter;

『 CPrinter クラスのポインタってことはこれが Version 17.20 ( No.375 ) 
の pcPrinter 変数にあたるってことね』
「そういうこと。で、このポインタをメンバ変数として持つ代わりに、
CSmartPointer クラスの変数はポインタでは扱いません」

・スマートポインタはポインタではなく普通の変数として使う。

「これが以下の部分」

CSmartPointer GetPrinterInstance( int p_iFlag )
{
    // フラグによって出力を変更します。
    if( p_iFlag == DEBUG_PRINTER )
    {
        CSmartPointer cSmartPointer( new CDebugPrinter() );
        return cSmartPointer;
    }
    // else
    CSmartPointer cSmartPointer( new CDlgPrinter() );
    return cSmartPointer;
}

『ホントだ、 GetPrinterInstance() 関数の戻り値がポインタじゃない』
「ポインタを使わない最大の理由は、変数のコピーをしたり変数がなくなる
ときにメンバ関数を呼び出すようにするため」
『あ、だからこれだけメンバ関数があるんだ』

CSmartPointer( const CSmartPointer &p_rcSmartPointer )
~CSmartPointer()
CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )

『コピーコンストラクタにデストラクタ、あと = 演算子のオーバーロード
だね』
「ポインタだとこういったメンバ関数が呼び出されないからね」
『呼び出して何をするの?』
「【参照カウンター】っていうのを増やしたり減らしたりします」
『さんしょうかうんたー?』
「そう。まず注意して欲しいのは、この参照と Version 3.22 ( No.047 ) 
の参照はまったく別だから注意してね」
『ってことは、一般的な意味でのさんしょー、ってこと?』
「そういうこと。つまり参照カウンターは〈指し示している数〉、もっと
具体的に言うと〈ある変数のアドレスを格納しているポインタの数〉を数え
るカウンターということです」
『ポインタの数!?』
「たとえば」

    int i;

    int *pi1 = &i;
    int *pi2 = &i;
    int *pi3 = &i;

「この i 変数のアドレスは、 pi1 、 pi2 、 pi3 の3つのポインタが持っ
ています。これは〈 i 変数の参照数は 3 〉ということ」
『つまり参照カウンター = 3 、って感じ?』
「そういうこと!」
『でもこれを数えるとどういう意味があるの?』
「この意味が出てくるのは、 new で変数を作ったとき。たとえば、以下の
ように今度は変数を new で作ったとします」

    int *pi1 = new int;
    int *pi2 = pi1;
    int *pi3 = pi1;

「この new で作った変数を delete するタイミングは、たったひとつ」

・pi1 、 pi2 、 pi3 の3つのポインタがなくなったとき。

「です」
『つまり、アドレス持ってるポインタが全部なくなった時に delete すれば
いいってことね』
「そこで、こういうことができれば適切なタイミングで delete ができる、
ということになります。つまり」

・アドレスを持つポインタの個数(=参照カウンター)を常に把握して
 おいて、その個数がゼロになったら delete する。

「ができればいいってこと」
『なるほど、 new int して返されたアドレスを持つポインタを数えておい
て、ぜーんぶなくなったら delete する……って、そんなことホントにでき
るの!?』
「もちろん。そのためのスマートポインタだからね。まず重要なのは、今み
たいにポインタのコピーをしたら、ポインタの個数を数えることはできない
っていう点」
『ポインタのコピーだと、さっきのメンバ関数が呼ばれないから?』
「そういうこと。ポインタのコピーはあくまでアドレスっていう整数値を
コピーするだけだから、 = 演算子のオーバーロード関数とかを作っておい
ても呼ばれないんです」
『そのために、普通の変数にする!』
「ってこと。そうすることで各種メンバ関数が呼ばれるようになるから。
それをひとつずつ見ていきます。まずはコンストラクタから」

        CSmartPointer cSmartPointer( new CDebugPrinter() );

『普通に CSmartPointer クラスの変数を作って、そこに new で作った
CDebugPrinter クラスの変数のアドレス渡してる、ってことね』
「ここで呼ばれるコンストラクタは以下のメンバ関数です」

    // コンストラクタでポインタを渡します。
    CSmartPointer( CPrinter *p_pcPrinter )
        : m_pcPrinter( p_pcPrinter )  ← 引数をメンバ変数にセット。
        , m_piRefCounter( new int )   ← 参照カウンターをセット。
    {
        // 参照カウンターに 1 をセットします。
        *m_piRefCounter = 1;
    }

「まず引数で渡されたアドレスを m_pcPrinter メンバ変数にセットしま
す。次に参照カウンターを作成します。参照カウンターは int 型変数を new 
で作って m_piRefCounter にセットします」

                             ↓参照カウンターを new で作成。
        , m_piRefCounter( new int )
              ↑そのアドレスをメンバ変数にセット。

『え、参照カウンターって new で作るの!?』
「そう、なぜかっていうと参照カウンターは各ポインタで共有しなきゃいけ
ないから。さっきの例で言うと……」

    int *pi1 = new int;
    int *pi2 = pi1;
    int *pi3 = pi1;

「この pi1 、 pi2 、 pi3 でひとつの参照カウンターを共有しないと、
ちゃんとカウントできないからね」
『それぞれで参照カウンターを持っちゃったら、全部ばらばらになっちゃう
んだ』
「それを避けるために、参照カウンターは new で作って、メンバ変数でそ
のアドレスを持つようにするわけです。図にするとこんな感じ」

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

『 cSmartPointer 変数にはポインタしかないんだね』
「ひとつは new CDebugPrinter で作った変数を、もうひとつは new int で
作った変数を指しているわけです。ここまでが以下の行ね」

        CSmartPointer cSmartPointer( new CDebugPrinter() );

「で、まず重要なのは、これを戻り値で返す部分」

        return cSmartPointer;

「実はここでかなり複雑なことが行われています」
『え”』
「ここで呼び出し元を見てみます」

    // 出力用に、 CSmartPointer クラスを受け取ります。
    // まずデバッグ用を受け取ります。
    CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );

「 Version 2.10 ( No.021 ) で説明したように関数を呼び出すと〈戻り値
に置き換わる〉から」
『 GetPrinterInstance() が CSmartPointer クラスの変数に置き換わる、
つまり cSmartPointer に置き換わるってことよね』
「前半は合ってるけど後半は間違い。 cSmartPointer 変数に置き換わるん
じゃなくて、新しく変数が作られるんです」
『ええっ!?』
「というわけで次回に続く!」

/*
    Preview Next Story!
*/
『なんっかすっごく嫌な予感がします!』
「うん、ここはかなり難しいところだからね」
『どの辺が難しい?』
「プログラムの見えない所で色々と動くところが」
『そうよそれ! なんかそういうの一番ムカツク!!』
「というわけで次回」
< Version 17.25 戻り値の一時変数 >
『につづく!』
「まぁそこが便利な所でもあるんだけどね」
『うっ』
 
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
RSSに登録
del.icio.us 登録する
Yahoo!ブックマーク 詳細を表示 users
livedoorクリップ 詳細を表示 livedoorクリップ ブックマーク数
はてなブックマーク 詳細を表示 はてなブックマーク ブックマーク数
 
このページは、Visual C++ 6.0を用いた C++ 言語プログラミングの解説を行う#pragma twiceの一コンテンツです。
詳しい説明は#pragma twiceのトップページをご覧ください。