読者です 読者をやめる 読者になる 読者になる

スマートポインタの使い方 その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;
}

このコードはハンドルの型がポインタではなく整数型である例。「構築」と「破棄」がペアにさえなっていればスマートポインタとして管理できる。

*1:コンパイルエラー"E2466 void & は有効な型ではない"が発生。g++やVC++だとOKなので実装の問題かな…。バグっぽいからQC送りしないと。