自作アロケータ

仕事で作っているモジュールは大きなメモリを自前でとってはいけないというルールがあります。正確にいうと、搭載先が自前でメモリループを持っており、そこから取って欲しいということでAPIが用意されています。しかしこちらとしては他にも載せられるようにするために独自APIをモジュール内部に持ち込みたくないということもあり、インターフェースとして、内部で利用するメモリ量を返してその分のメモリを外で取ってもらってその先頭ポインタを受け取るという形にしています。

class Module
{
	・・・
	
	size_t GetWorkspaceSize();
	void SetWorkspace( void* pWorkspace );
};

あとは受け取ったメモリの塊りをごにょごにょと割り当てれば移植性の高いモジュールになるわけである。

で、最近困っているのがSTLである。今まではでかいメモリといえば画像の一時置き場だったので1次元のメモリの塊りでも特に扱いはこまらなかった。しかしSTLを外部から取ろうと思うとアロケータを自作するしかない。仕様としては、外部からのメモリの塊りを受け取ってそこからアロケートし、足りなくなったらデフォルトのnew演算子を呼ぶ・・・というアロケータを作成するわけである。正直言うと私の技術力ではちょっと足りなかった。しかし一応曲りなりとも動くものが出来た。

#pragma once
template <class T>
class MemoryPoolAllocator
{
public:
	// 型定義
	typedef size_t size_type;
	typedef ptrdiff_t difference_type;
	typedef T* pointer;
	typedef const T* const_pointer;
	typedef T& reference;
	typedef const T& const_reference;
	typedef T value_type;
	
	// アロケータをU型にバインドする
	template <class U>
	struct rebind
	{
		typedef MemoryPoolAllocator<U> other;
	};
	
	// コンストラクタ
	MemoryPoolAllocator( const MemoryPoolAllocator& alloc ):
		m_pPool( alloc.m_pPool ),m_PoolSize( alloc.m_PoolSize ),m_pPoolEnd( alloc.m_pPoolEnd ),m_ppNow( alloc.m_ppNow ){}

	template< class U >
	MemoryPoolAllocator( const MemoryPoolAllocator< U >& alloc ):
		m_pPool( alloc.m_pPool ),m_PoolSize( alloc.m_PoolSize ),m_pPoolEnd( alloc.m_pPoolEnd ),m_ppNow( alloc.m_ppNow ){}

	MemoryPoolAllocator( void* aPoolMemory, size_t PoolSize ):
		m_pPool( reinterpret_cast< unsigned char* >( aPoolMemory ) ),
		m_PoolSize( PoolSize ),
		m_pPoolEnd( reinterpret_cast< unsigned char* >( aPoolMemory ) + PoolSize ),
		m_ppNow( reinterpret_cast< unsigned char** >( m_pPool ) ){
			if( m_ppNow != 0 ){
				if( sizeof(unsigned char*) < PoolSize ){
					//プールの先頭をm_ppNowに割り当てる。
					*m_ppNow = m_pPool + sizeof(unsigned char*);
				}
			}
		}
	
	// デストラクタ
	~MemoryPoolAllocator() throw(){}
	
	// メモリを割り当てる
	pointer allocate(size_type num, const void* /*p*/ ){
		allocate(num);
	}
	pointer allocate(size_type num )
	{
		if( m_ppNow != 0 ){
			unsigned char* pNext = *m_ppNow + num * sizeof(T);
			if( pNext <= m_pPoolEnd ){
				unsigned char* pNow = *m_ppNow;
				*m_ppNow = pNext;
				return reinterpret_cast< pointer >( pNow );
			}else{
				return (pointer)( ::operator new( num * sizeof(T) ) );
			}
		}else{
			return (pointer)( ::operator new( num * sizeof(T) ) );
		}
	}
	// 割当て済みの領域を初期化する
	void construct(pointer p, const T& value)
	{
		new( (void*)p ) T(value);
	}
	
	// メモリを解放する
	void deallocate(pointer p, size_type /*num*/ )
	{
		if( m_ppNow != 0 ){
			if( m_pPool <= reinterpret_cast< unsigned char* >( p ) && reinterpret_cast< unsigned char* >( p ) < m_pPoolEnd ){
			}else{
				::operator delete( (void*)p );
			}
		}else{
			::operator delete( (void*)p );
		}
	}
	// 初期化済みの領域を削除する
	void destroy(pointer p)
	{
		p->~T();
	}
	
	// アドレスを返す
	pointer address(reference value) const { return &value; }
	const_pointer address(const_reference value) const { return &value; }
	
	// 割当てることができる最大の要素数を返す
	size_type max_size() const throw()
	{
		return -1;
	}

	template< class U >
	bool operator==( const MemoryPoolAllocator< U >& a ) throw() { return m_pPool == a.m_pPool; }
	template< class U >
	bool operator!=( const MemoryPoolAllocator< U >& a ) throw() { return m_pPool != a.m_pPool; }
	
private:
	//メモリプール
	unsigned char* m_pPool;
	const unsigned char* m_pPoolEnd;
	size_t m_PoolSize;
	//現在位置
	unsigned char** m_ppNow;

	friend class MemoryPoolAllocator;
};

参考文献はこちら
http://www.geocities.jp/ky_webid/cpp/library/028.html
http://docs.sun.com/source/820-2985/general/15_3.htm
メモリ管理のアルゴリズムは至って単純で、先頭ポインタから確保された分だけポインタを前に進める。メモリの最後まできたらデフォルトのnewに切り替える。メモリの開放はブロック内なら無視でデフォルトのnewならdeleteを呼ぶ。ただそれだけである。本当なら、ブロック内の開放された領域を再利用出来るようにしたかったのだが私の技術力では到底実現できそうにない(あと時間もない)のであきらめました。だれかいい実装があればよろしく(ぉぃ
かなり限定的な仕様なのでどれだけ実用的かは不明ですがうちの製品には必須のアイテムです(^^;


ちなみに、デバッグ中に気がついたことですが、参考ページの一部に誤りがあるっぽいです。
>>この場合、独自のアロケータCMyAllocatorが確保する型はint型な訳です。つまり、int型用のアロケータなので、各要素を表現する構造体の型とは一致するはずがありません。従って、std::listは、プログラマがせっかく定義したアロケータを使うことがないことになります。<<
デバッグにはlistではなくdequeを使っていましたが、コンテナのアイテム以外にもアロケータがかなり使われていました。つまり、テンプレート引数としてAllocator< int >としても確保されるのがint型だけとは限らないようです。じゃあSTLではそれをどうやって実現しているか?デバッガを追ってみて気がつきました。簡単に書くと以下のようになってます。

template< typename _Ty, typename _Ax = allocator<_Ty> >
class Container
{
public:
	Container( const _Ax& _al );
};
//↑こんなコンテナがあったとして、
Container< int, MyAlloc< int > > container( MyAlloc< int >() );
//↑こんなアロケータをセットしたとすると、
_Ax::rebind< double >::other otherAlloc( _al );
//↑これで別の型のアロケータにコンバートできる。

上記の通り、テンプレート引数のアロケータの型がintであってもdouble型でアロケートすることは出来ます。このためにrebindという構造体があったんですね。最初は結構疑問だったんですが(^^;
実際、listに渡してみてデバッグを追ってみればわかりますがちゃんとアロケータが使われています。