Version 17.23
スマートポインタ
「前回は delete で解放する方法についていくつか説明しました」
『なんかトリッキーな感じだったんだけど……』
「まぁ、メモリを自由に操作できる C++ 言語だから、それだけ自由度が必要
ってところかな」
『自由すぎ……』
「さて、今回はもうひとつの問題について」
・ポインタをいつ解放するか
・使用中に解放してはいけない(存在しない変数を使ってしまう)
・解放し忘れてはいけない(メモリリークになる)
・解放前にアドレスを上書きしてはいけない(同上)
『これは結構大きな問題よね』
「この問題を解決するための方法のひとつが【スマートポインタ】というも
のです」
『やせてるポインタ?』
「違います。スマート、っていうのは利口な、っていう意味」
『あー、そういえばそういう意味で使うことあるよね。スマートなやり方と
か』
「そうそう、そっちの意味。スマートポインタの考え方は」
・スマートポインタはクラス。
・中に、実際に使用するクラスのポインタを持つ。
・スマートポインタはポインタではなく普通の変数として使う。
・【参照カウンター】へのポインタをメンバ変数に持つ。
・複製ができたら参照カウンターを増やし、変数が消えたら減らす。
・参照カウンターがゼロになったときだけ解放する。
「というものです」
『わけわかりません』
「なので実際に例を使って見てみましょう。まずはプログラム全文」
// Main.cpp
#include <Windows.h>
#include <stdio.h>
// 「出力」を行うクラスのインターフェイス。
class CPrinter
{
public:
// 出力関数。純粋仮想関数です。
virtual void Output( const char *p_pch ) = 0;
};
// 「デバッグ出力」を行うクラス。
class CDebugPrinter : public CPrinter
{
public:
// 出力関数。オーバーライドしています。
void Output( const char *p_pch )
{
// デバッグ出力します。
OutputDebugString( p_pch );
}
};
// 「ダイアログ出力」を行うクラス。
class CDlgPrinter : public CPrinter
{
public:
// 出力関数。オーバーライドしています。
void Output( const char *p_pch )
{
// ダイアログに出力します。
MessageBox( NULL, p_pch, "デバッグ", MB_OK );
}
};
// スマートポインタクラス。
class CSmartPointer
{
// これが対象のポインタ。
CPrinter *m_pcPrinter;
// 参照カウンターへのポインタ。
int *m_piRefCounter;
public:
// コンストラクタでポインタを渡します。
CSmartPointer( CPrinter *p_pcPrinter )
: m_pcPrinter( p_pcPrinter )
, m_piRefCounter( new int )
{
// 参照カウンターに 1 をセットします。
*m_piRefCounter = 1;
}
// コピーコンストラクタ。
CSmartPointer( const CSmartPointer &p_rcSmartPointer )
: m_pcPrinter( p_rcSmartPointer.m_pcPrinter )
, m_piRefCounter( p_rcSmartPointer.m_piRefCounter )
{
AddRef();
}
// デストラクタ。
~CSmartPointer()
{
Release();
}
// = 演算子をオーバーロードします。
CSmartPointer &operator =( const CSmartPointer &p_rcSmartPointer )
{
if( m_pcPrinter != p_rcSmartPointer.m_pcPrinter )
{
// 対象ポインタのアドレスが違う場合は
// 参照カウンターを減らします。
Release();
}
// コピーします。
m_pcPrinter = p_rcSmartPointer.m_pcPrinter;
m_piRefCounter = p_rcSmartPointer.m_piRefCounter;
// 参照カウンターを増やします。
AddRef();
return *this;
}
// 対象ポインタが持つアドレスを返します。
CPrinter *GetPointer()
{
return m_pcPrinter;
}
private:
// 参照カウンターを 1 増やします。
void AddRef()
{
// 参照カウンターをひとつ増やします。
++( *m_piRefCounter );
}
// 参照カウンターを 1 減らし、ゼロなら解放します。
void Release()
{
// 参照カウンターをひとつ減らします。
--( *m_piRefCounter );
// 参照カウンターがゼロになったら解放します。
if( *m_piRefCounter == 0 )
{
delete m_pcPrinter;
delete m_piRefCounter;
}
}
};
// フラグ用定数値。
const int DEBUG_PRINTER = 0;
const int DLG_PRINTER = 1;
// 「出力」クラスを返す関数。
// 引数が DEBUG_PRINTER なら CDebugPrinter クラスのポインタを、
// DLG_PRINTER なら CDlgPrinter クラスのポインタを返します。
CSmartPointer GetPrinterInstance( int p_iFlag )
{
// フラグによって出力を変更します。
if( p_iFlag == DEBUG_PRINTER )
{
CSmartPointer cSmartPointer( new CDebugPrinter() );
return cSmartPointer;
}
// else
CSmartPointer cSmartPointer( new CDlgPrinter() );
return cSmartPointer;
}
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;
}
『長!』
「最初の CPrinter 、 CDebugPrinter 、 CDlgPrinter の3クラスは
Version 17.20 ( No.375 ) の時のものと同じです」
『あ、ホントだ』
「残りは次回説明します!」
/*
Preview Next Story!
*/
『久々に長いプログラムねー』
「スマートポインタは複雑だから」
『メンバ関数がいっぱいある……』
「でも、これが理解できればクラスの大部分が理解できたことになるかも」
『おお!』
「というわけで次回」
< Version 17.24 スマートポインタと参照カウンター >
『につづく!』
「ま、それだけ難しいってことなんだけどね」
『なんですとー!』