スマートポインタの使い方 その3:メモリ以外のリソースをスマートポインタで管理する
デリーターとは
スマートポインタが管理できるのは、メモリ領域だけではなく「構築」と「破棄」がペアになっている任意のリソースも管理できる。
例えば、Cのfopen関数でオープンしたファイルをfclose関数でクローズしたり、あるいは、Windows APIのGDIオブジェクトをDeleteObject関数で破棄したりとか。
その仕組みを実現するのが「デリーター」で、厳密にはスマートポインタのデフォルトのデリーターが"operator delete"となっている。
#pragma hdrstop #include <windows.h> #include <boost/tr1/memory.hpp> // スマートポインタがGDIオブジェクトを破棄するデリーター struct GDIDeleter { void operator()(HANDLE handle) { ::DeleteObject(handle); } }; void foo() { std::tr1::shared_ptr<void> Pen(::CreatePen(PS_SOLID, 20, RGB(255,255,0)), GDIDeleter()); ::SelectObject(hDC, Pen.get()); // ハンドルはgetメソッドで取得 }
shared_ptrの場合は、コンストラクタにデリーターのインスタンスを渡す。デリーターは関数オブジェクトなのでoperator()でリソースの破棄を行う処理を記述する。
unique_prtの場合は、型宣言のテンプレートにデリーターの型を記述する。
void foo() { std::unique_ptr<void, GDIDeleter> Pen(::CreatePen(PS_SOLID, 20, RGB(255,255,0))); ::SelectObject(hDC, Pen.get()); // ハンドルはgetメソッドで取得 }
C++Builder(BCC32)でのちょっとした問題とその回避
C++Builderではvoidへのポインタをstd::unique_ptrで管理出来ない*1ので適当な型へのポインタとしてキャストするか、以下の例のようにラッパークラスを作成して誤魔化す。
//--------------------------------------------------------------------------- #include <memory> #include <boost/shared_ptr.hpp> #include <tchar.h> #include <iostream> //--------------------------------------------------------------------------- typedef int MAPHANDLE; MAPHANDLE LoadMap(const char* MapFilePath) { MAPHANDLE MapID = 100; // あくまでもダミー std::cout << "Loaded Map ID = " << MapID << std::endl; return MapID; } //--------------------------------------------------------------------------- void ReleaseMap(MAPHANDLE MapID) { std::cout << "Release Map ID = " << MapID << std::endl; } //--------------------------------------------------------------------------- void ProcessMap(MAPHANDLE MapID, int Code) { std::cout << "Process Map ID = " << MapID << std::endl; } //--------------------------------------------------------------------------- class CMapHandle { private: //typedef void Ty_; // C++Builderだと、void*はstd::unique_ptrで管理できない。orz typedef int Ty_; public: CMapHandle(MAPHANDLE hMap) : instance((Ty_ *)hMap) { } operator MAPHANDLE() const { return (MAPHANDLE)instance.get(); } private: class Deleter { public: void operator()(Ty_* ptr) { ReleaseMap((MAPHANDLE)ptr); } }; std::unique_ptr<Ty_, Deleter> instance; }; //--------------------------------------------------------------------------- int _tmain(int argc, _TCHAR* argv[]) { // 地図ファイルを読み込む CMapHandle hMap(LoadMap("D:\\Maps\\test1.shp")); // 何らかの処理 ProcessMap(hMap, 1); // ReleaseMap(hMap); // 後始末はデリーターが行うので不要 return 0; }
このコードはハンドルの型がポインタではなく整数型である例。「構築」と「破棄」がペアにさえなっていればスマートポインタとして管理できる。