リファレンスカウンタの薦め
「リファレンスカウンタ」あるいは「参照カウンタ」は「そのインスタンスを参照(リファレンス)しているオブジェクトが幾つあるか」というカウンタです
つまりオブジェクトのインスタンスの寿命を管理するものです
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つだけですが、この辺の管理が慣れないうちはちょっと面倒でしょうか
でもリファレンスカウンタ付きのオブジェクトというのは特にリソースリークの検出などに威力を発揮するので覚えておいて損はないですよ
というところで、今回はこの辺で