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