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 スマートポインタの自動解放 >
『につづく!』
「ま、楽しくなってはまるとそれはそれで問題だけど」
『だめじゃん』