簡易メモリアロケータ

最近面白そうな機能満載の携帯がいっぱい出てきて、意味無く携帯を新しくしたいです。どっちかっていうと、時間潰しができて遊べる携帯(^^;;; 早くDOCOMOからiPhone出てくれないかな~。坂本龍一がCMやってたSamsungも良いなぁ。面白そうなやつは全部ソフトバンクなんだなぁ~。DOCOMOももっと変わったもん出して欲しいなぁ。

そんなことはさておき、

ローカルスコープ内で、使い捨てみたいな使い方をするような小さなメモリを確保するとき、たとえば、Win32APIでなんらかの文字列を受け取るときのバッファとか、いちいちnewでメモリ確保して、使い終わったらdeleteして・・・が面倒くさい。特にC#を覚えてnewしっぱなしでdeleteする必要がない言語に慣れきってしまうとC++は非常にやりづらい。

と、かなり前に思って、以前ずっとかなり前にWin32APIの勉強のため書いていた簡易メモリアロケータを引っ張り出してきて、ちょこっと手直し。

正直、メモリアロケータなんて大層なもんじゃなくて、コンストラクタでnewしてメモリを確保して、スコープから抜けるとデストラクタでdeleteする、という超簡単なもん。

手直ししたのは、コンストラクタとデストラクタでメモリの確保と解放する際に、newとdelete演算子を使わず、Windowsの Heap系のAPIで置き換えたことと、& 演算子をオーバーロードして、内部バッファのポインタを返すようにしたことの2点。Advanced Windows のメモリ管理を参考に参照カウンタも付けてみました(^^;

/*
  若干修正(2009/3/20)
  メモリ確保は一回だけ(2009/3/21)
  キャスト演算子のオーバーロード追加(2009/3/21)
*/
#define HEAP_INITIAL_SIZE 0
#define HEAP_MAX_SIZE 0
#include <windows.h>
class CAllocator
{
private:
  //コピーと代入は禁止!
  CAllocator &operator=(const CAllocator& rhs){}
  CAllocator(const CAllocator& src){}

  //static なヒープハンドルと参照カウンタ
  static HANDLE s_hHeap;
  static UINT s_cbRef;

  LPVOID m_lpBlock;

  void Init()
    {
      if(s_hHeap == NULL)
        s_hHeap = HeapCreate(HEAP_NO_SERIALIZE,
                             HEAP_INITIAL_SIZE,
                             HEAP_MAX_SIZE);
    }

public:
  //コンストラクタとデストラクタ
  CAllocator() //
       :m_lpBlock(NULL)
    {
      Init();
    }

  CAllocator(UINT size)
       :m_lpBlock(NULL)
    {
      Init();
      Alloc(size);
    }

  virtual ~CAllocator()
    {
      if(m_lpBlock != NULL)
        {
          if(TRUE == HeapFree(s_hHeap,HEAP_NO_SERIALIZE,m_lpBlock))
            {
              m_lpBlock = NULL;

              if(s_hHeap != NULL && --s_cbRef == 0)
                {
                  if(TRUE == HeapDestroy(s_hHeap))
                    s_hHeap = NULL;
                }
            }
        }
    }

  //プライベートヒープからメモリ確保
  void Alloc(UINT size)
    {
      if((m_lpBlock == NULL) &&
         ((m_lpBlock = HeapAlloc(s_hHeap,HEAP_NO_SERIALIZE,size)) != NULL))
        s_cbRef++;
    }

  LPVOID operator&()
  LPVOID operator*()
    {
      return m_lpBlock;
    }

  operator LPVOID()
    {
      return m_lpBlock;
    }
};

HANDLE CAllocator::s_hHeap = NULL;
UINT CAllocator::s_cbRef = 0;

ほとんどサンプルと同じになっちゃった(^^;

具体的には・・・

CAllocator alloc(MAX_PATH); //MAX_PATHバイトのメモリ確保
CAllocator allocs[5] = {100,100,100,100,100}; //配列とか、
CAllocator alloc; alloc->Alloc(100); //などでメモリ確保
CAllocator alloc; alloc.Alloc(100); //などでメモリ確保

メモリブロックのポインタを得るには、*演算子。

LPSTR lpStr = reinterpret_cast<LPSTR>(*alloc);

スコープを抜けるとデストラクタがコールされて、メモリが解放。参照数がゼロになると、プライベートヒープハンドルも解放。

修正すべき欠点は、スレッドセーフじゃない。複数スレッドで使用すると参照カウンタとかで競合が起きるし。大きなメモリ(たとえば1MBとか)を確保したいときとかは、VirtualAlloc系を使うべき、らしい。
まぁ・・・スレッドをおこすようなプログラムは複雑になればなるほどC#の方が安全で簡単なんで。

追記) &演算子で確保したメモリのポインタをリターンしてますが、スコープを抜けると問答無用でメモリ解放してしまうので、気をつけないといけません。CAllocatorを動的に確保すればいいんでしょうけど、それだと意味がなくなる(^^;

これをベースに実用的に使えるまで改良していきたいと思います。


8月30日、ところどころ修正しました。