リファレンスカウンタの薦め

「リファレンスカウンタ」あるいは「参照カウンタ」は「そのインスタンスを参照(リファレンス)しているオブジェクトが幾つあるか」というカウンタです
つまりオブジェクトのインスタンスの寿命を管理するものです

Fig.1 を見てください
実装はたったこれだけ。カウンタが 0 になったらオブジェクトを解放するというだけのことです。簡単ですね
ただ、Release というメソッドで delete this としているところからお分かりの様にこの「リファレンスカウンタ」を使うオブジェクトは普通 new を使ってインスタンスを生成します
というのも new を使わない自動変数や静的変数ではその寿命はコンパイル時に既に決まってしまっているのでわざわざその寿命を管理するための仕組みを用意する意味がないからです
Fig.1

class CRefCounterSample {
	LONG	m_cRef;
	・・・
public:
	void	AddRef();
	void	Release();
	
	CRefCounterSample();
	~CRefCounterSample();
};

CRefCounterSample::CRefCounterSample():
m_cRef(1), // リファレンスカウンタは必ず「1」で初期化する。なぜなら今まさに作られようとしている自分自身が存在するから
・・・
{
}
void	CRefCounterSample::AddRef(){
	++m_cRef;
}
void	CRefCounterSample::Release(){
	if( --m_cRef == 0 ){
		delete this;
	}
}

さて、こんなサンプルクラスだけ見ても何のことやら実感が湧かないと思いますので 前回の講義で使った CFile に「リファレンスカウンタ」の機能を追加してみましょうか
Fig.2

struct IFile {
	virtual void	AddRef() = 0;
	virtual void	Release() = 0;
	virtual bool	Open( const char*	path, const char*	fopen_mode ) = 0;
	virtual void	Close() = 0;
	virtual size_t	Read( void*	pBuffer, size_t	cbBlock, size_t	cBlocks ) = 0;
	virtual long	Seek( long	lOffset, int	nFlags ) = 0;
	virtual long	Tell() = 0;
};
class CFile : public IFile {
	LONG	m_cRef;
	FILE*	m_fp;
public:
	virtual void	AddRef();
	virtual void	Release();
	virtual bool	Open( const char*	path, const char*	fopen_mode );
	virtual void	Close();
	virtual size_t	Read( void*	pBuffer, size_t	cbBlock, size_t	cBlocks );
	virtual int	Seek( long	lOffset, int	nFlags );
	virtual long	Tell();
	
	static IFile* CreateInstance();
	
	CFile();
	virtual ~CFile();
};
CFile::CFile():
m_cRef(1),
m_fp(NULL)
{
}
void	CFile::AddRef(){
	++m_cRef;
}
void	CFile::Release(){
	if( --m_cRef == 0 ){
		delete this;
	}
}

新しく追加した AddRef() というメソッドに「リファレンスカウンタを増やす」という機能を持たせ、それに対応する Release() に「リファレンスカウンタを減らす」という機能を持たせました
また今までは Destroy() が呼ばれたらすぐにオブジェクトを解放していたのに対し、ここでは Release() が呼ばれても「リファレンスカウンタ」を減らすだけですぐにはオブジェクトを解放しないようになりました
オブジェクトを解放するのはそのオブジェクトの「リファレンスカウンタ」が 0 になったときだけです
さて、どうでしょうか。たったこれだけの実装で CFile は「リファレンスカウンタ」の機能を手に入れました。これまた簡単ですね

実装は簡単なんですが、この「リファレンスカウンタ付きオブジェクト」を使う側では少し気を使わないといけません
「リファレンスカウンタ付きオブジェクト」を使う側の例としてFig.3 のようなクラスを考えてみましょう
Fig.3

class CSampleApp {
	std::vector<IFile*>	m_files;
	
	HRESULT	GetFile( UINT	idFile, IFile**	ppFile );
public:
	DWORD	Load( DWORD	cFiles, LPCTSTR*	ppFiles );
	void	Unload();
	.
	.
	.
	long	Tell( UINT	idFile );
	
	CSampleApp();
	~CSampleApp();
};
DWORD	CSampleApp::Load( DWORD	cFiles, LPCTSTR*	ppFiles ){
	DWORD	cFilesLoaded = 0;
	m_files.reserve( cFiles );
	for( DWORD	n = 0; n < cFiles; ++n ){
		IFile*	pFile = FileCreate(); // オブジェクトのインスタンスが作られた直後のリファレンスカウンタは「1」だよ
		if( pFile && !pFile->Open( ppFiles[n], _T("rb"))){
			pFile->Release();
			pFile = NULL;
		}
		m_files.push_back( pFile );
	}
	return	( DWORD )m_files.size();
}
void	CSampleApp::Unload(){
	for( std::vector<IFile*>::iterator p = m_files.begin(); p != m_files.end(); ++p ){
		if( *p ){
			(*p)->Release();
		}
	}
	m_files.clear();
}
HRESULT CSampleApp::GetFile( UINT	idFile, IFile**	ppFile ){
	if( ppFile ){
		*ppFile = NULL;
	}
	HRESULT hresult = S_OK;
	if( idFile >= m_files.size()){
		hresult = E_INVALIDARG;
	}
	if( SUCCEEDED( hresult )){
		IFile*	pFile = m_files[idSound];
		if( !pFile ){
			hresult = HRESULT_FROM_WIN32( ERROR_OBJECT_NOT_FOUND );
		}
		if( SUCCEEDED( hresult )){
			pFile->AddRef();// この関数の外(→スコープ外)に渡すので AddRef() してることに注意。(→受け取り側は使い終わったら Release() を呼ぶ必要がある。)
			*ppFile = pFile;
		}
	}
	return	hresult;
}
.
.
.
long	CSampleApp::Tell( UINT	idFile ){
	long	lOffset= -1;
	IFile*	pFile = NULL;
	HRESULT	hresult = GetFile( idFile, &pFile );// GetFile の中で AddRef() してることに注意。(→使い終わったら必ず Release() を呼ぶこと)
	if( SUCCEEDED( hresult )){
		lOffset = pFile->Tell();
		pFile->Release();
		pFile = NULL;
	}
	return	lOffset;
}

ここで注目してもらいたいのは GetFile() と Tell() の実装です
GetFile() では、出力パラメータにポインタを格納するときに AddRef() を呼んでいますね
これは関数の外、即ち自分の関知しているスコープの外にオブジェクト(を指すポインタ)を渡すのでやっていることです
これは1つ目のルールとして覚えてください
つまり「スコープ外にオブジェクト(を指すポインタ)を渡す場合には必ず AddRef() を呼び出してリファレンスカウンタを増やす必要がある」というルールです
また Tell() では GetFile() から貰ったオブジェクトを使い終わったところで Release() を呼んでいますね
これも2つ目のルールとして覚えてください
つまり「使い終わったオブジェクト(を指すポインタ)は必ず Release() を呼び出してリファレンスカウンタを減らす必要がある」というルールです
ルールはこの2つだけですが、この辺の管理が慣れないうちはちょっと面倒でしょうか
もリファレンスカウンタ付きのオブジェクトというのは特にリソースリークの検出などに威力を発揮するので覚えておいて損はないですよ

というところで、今回はこの辺で