Version 17.27
スマートポインタの自動解放、の続き
「では前回の続きから。まずこの行の話でした」
cSmartPointer = GetPrinterInstance( DLG_PRINTER );
『右側の GetPrinterInstance() 関数もスマートポインタを返すんだよね』
「そう、だからこの関数を呼び出している部分が戻り値に置き換わります」
CDebugPrinter クラスの変数のアドレスを持つ
↓ ↓CDlgPrinter クラスの変数のアドレスを持つ
cSmartPointer = cSmartPointer;
(左) (右)
『でも、左右の持ってるアドレスが違う、と』
「このとき、 = 演算子のオーバーロードメンバ関数が呼び出されます」
// = 演算子をオーバーロードします。
CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )
「この中でアドレスが異なる場合には Release() メンバ関数を呼び出すよ
うにしてあります。で、 Release() メンバ関数は、参照カウンターが0に
なったら持っている変数を delete します」
┌ CDebugPrinter クラスの変数 ─┐
→ │Output() │
両方の変数が └───────────────┘
deleteされます ┌ int 型変数 ┐ ↑
→ →→→│ 0 │ ↑
↑ └──────┘ ↑
┌ cSmartPointer(左)──┐ ↑ ↑
│m_piRefCounter →→→→→→ ↑
│m_pcPrinter →→→→→→→→→→→→→→→→→→→→→→
└───────────┘
『つまりこれが、〈スマートポインタが自動的に delete してくれた〉って
ことなんだよね』
「そういうこと。スマートポインタの最大のメリットは〈アドレスを誰も
持っていなかったら delete する〉っていう機能を持つこと。そうすること
で、適切なタイミングで delete されるわけです」
『まだ使っている時に delete することもないし、 delete し忘れて
メモリリークすることもない。必要なくなったらすぐに delete する、って
わけね』
「そういうこと。スマートポインタを使用することで〈使わなくなったら
delete する〉っていう処理が自動的にできるわけです」
『なるほど、確かに便利……』
「さて、これで cSmartPointer 変数(左)が持つふたつのメンバ変数が
空きました。ここに、 cSmartPointer 変数(右)のアドレスをコピーします」
// = 演算子をオーバーロードします。
CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )
{
// (略)
// コピーします。
m_pcPrinter = p_rcSmartPointer.m_pcPrinter;
m_piRefCounter = p_rcSmartPointer.m_piRefCounter;
// 参照カウンターを増やします。
AddRef();
「アドレスをコピーして AddRef() メンバ関数を呼び出すことで、以下のよ
うにふたつのスマートポインタからひとつの〈 CDlgPrinter クラスの変数〉
のアドレスを持つことになるわけです」
┌ cSmartPointer(左)──┐
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→ ↓
└───────────┘ ↓ ↓
↑1つめ ↓ ↓
┌ cSmartPointer(右)──┐ ┌ CDlgPrinter クラスの変数 ─┐ ↓
│m_pcPrinter →→→→→→→→→│Output() │ ↓
│m_piRefCounter →→→→→→ └──────────────┘ ↓
└───────────┘ ↓ ┌ int 型変数 ┐ ↓
↑2つめ →→│ 2←増えた │←←←←←←←←
└──────┘
『前回の最初と同じ状態、ってことね』
「そういうこと。この時点で、左右同じアドレスを持つわけです」
↓←←←←←←←↓CDlgPrinter クラスの変数のアドレスを持つ
cSmartPointer = cSmartPointer;
(左) (右)
『左の方のが削除されて、右のに置き換わったわけね……』
「で、同じように、 cSmartPointer(右) は関数の戻り値だから」
『なくなってデストラクタが呼び出されるわけね』
「そして Release() メンバ関数が呼び出されて参照カウンターが1減らされ
ます。だから図にするとこうなります」
┌ cSmartPointer(左)──┐
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→ ↓
└───────────┘ ↓ ↓
↓ ↓
┌ CDlgPrinter クラスの変数 ─┐ ↓
│Output() │ ↓
(なくなった) └──────────────┘ ↓
┌ int 型変数 ┐ ↓
│ 1←減った │←←←←←←←←
└──────┘
「こうして、 cSmartPointer(左) が持つアドレスが〈 CDlgPrinter クラス
の変数〉のアドレスに置き換わりました」
『おお』
「なので、次の行で GetPointer() メンバ関数を呼び出すと、この
〈 CDlgPrinter クラスの変数〉のアドレスが返されます」
// 出力します。
cSmartPointer.GetPointer()->Output( "あいうえお\n" );
『前回と同じで、ここでポリモーフィズムが機能してるわけね』
「そういうこと。 GetPointer() メンバ関数の戻り値の型は CPrinter クラス
のポインタだけど、実際にはその派生クラスの CDlgPrinter クラスの
アドレスが返される、というわけです」
『で、 Output() メンバ関数は仮想関数でオーバーライドされてるからそっ
ちが呼び出される、と。なるほどねー』
「さて、最後の締め」
『あれ? これで終わりじゃないの?』
「まだ残ってるよ。今 cSmartPointer 変数が持っている、
〈 CDlgPrinter クラスの変数〉と参照カウンターの〈 int 型変数〉を
delete しないと」
『あ、そか、それしないとメモリリークしちゃうね。でも、さっきは
= 演算子をオーバーロードしていて、それで = で代入したからそれが呼ば
れたけど、今度はどうするの?』
「今実行してる WinMain() 関数をもう一度見てみようか」
int WINAPI WinMain
( HINSTANCE p_hInstance
, HINSTANCE p_hPrevInstance
, LPSTR p_pchCmdLine
, int p_iCmdShow
)
{
// 出力用に、 CSmartPointer クラスを受け取ります。
// まずデバッグ用を受け取ります。
CSmartPointer cSmartPointer = GetPrinterInstance( DEBUG_PRINTER );
// 出力します。
cSmartPointer.GetPointer()->Output( "あいうえお\n" );
// 次にダイアログ用を受け取ります。
cSmartPointer = GetPrinterInstance( DLG_PRINTER );
// 出力します。
cSmartPointer.GetPointer()->Output( "あいうえお\n" );
←ここまで来た。
return 0;
}
「これから WinMain() 関数から抜けます。すると cSmartPointer 変数は?」
『なくなる……そか、デストラクタが呼ばれる!!』
「そういうこと。クラス型変数がなくなるときにはデストラクタが呼び出さ
れます」
『ってことは、 GetPrinterInstance() 関数から抜けるときと同じで、
デストラクタが呼び出されて、さらに Release() メンバ関数が呼び出され
ると』
// 参照カウンターを 1 減らし、ゼロなら解放します。
void Release()
{
// 参照カウンターをひとつ減らします。
--( *m_piRefCounter );
// 参照カウンターがゼロになったら解放します。
if( *m_piRefCounter == 0 )
{
delete m_pcPrinter;
delete m_piRefCounter;
}
}
「このメンバ関数では、まず参照カウンターがひとつ減ります」
┌ cSmartPointer(左)──┐
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→ ↓
└───────────┘ ↓ ↓
↓ ↓
┌ CDlgPrinter クラスの変数 ─┐ ↓
│Output() │ ↓
└──────────────┘ ↓
┌ int 型変数 ┐ ↓
│ 0←減った │←←←←←←←←
└──────┘
『参照カウンターが 0 になった!!』
「ということは、その下の if の中に入るので delete が実行されます」
delete m_pcPrinter;
delete m_piRefCounter;
『で、前回と同じように CDlgPrinter クラスの変数と int 型変数が解放
されるわけね』
┌ cSmartPointer(左)──┐
│m_piRefCounter →→→→→→→→→→→→→→→→→→→→→→→→
│m_pcPrinter →→→→→→→→→→→→→ ↓
└───────────┘ ↓ ↓
↓ ↓
┌ CDlgPrinter クラスの変数 ─┐ ↓
→ │Output() │ ↓
両方の変数が └──────────────┘ ↓
deleteされます ┌ int 型変数 ┐ ↓
→ │ 0 │←←←←←←←←
└──────┘
「というわけで、 WinMain() 関数から抜けたときには、 new で作られた
ふたつの変数はちゃんと delete されて、メモリリークは起きない、という
わけです」
『すご……』
/*
Preview Next Story!
*/
『もう頭がこんがらがっちゃう!』
「まぁ、これは作るのも使うのも難しいからね」
『……使うくらいならなんとかならない?』
「実は使う時も注意が必要なんです」
『ええー!?』
「というわけで次回」
< Version 17.28 スマートポインタのまとめ >
『につづく!』
「こういう奥の深さが面白くなってくると……」
『それマゾよ絶対!』