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 戻り値の一時変数 >
『につづく!』
「まぁそこが便利な所でもあるんだけどね」
『うっ』