STLで自作アロケータ

投稿日:

STLのvectorやmapを使用する機会が増えてきたので、自作のアロケータを作りメモリ使用量などを監視できるようにします。また将来、独自のメモリ管理に置き換えられるようにしておく。


STLのアロケータ

メモリ確保を行うためのクラス(アロケータ)をテンプレートの引数に指定しますが、通常は引数を省略するためデフォルトのアロケータになります。このアロケータを自作、指定することで独自のメモリ管理を行えます。

//vectorの定義 VS2012
// TEMPLATE CLASS vector
template<class _Ty,
	class _Alloc = allocator<_Ty> >
class vector;

自作アロケータの定義

VisualStudio2012(Update2)のデフォルトアロケータを参考に自作アロケータを定義。STLコンテナが要求するメンバ関数、型定義、演算子などを定義することでSTL用のアロケータとして利用できます。異なる型同士のやり取りを定義する「struct rebind」や==演算子に注意が必要です。今回は、どの型でも同じメモリ管理関数を使用しているため簡単な定義のみで済んでいます。
一部のVS2012依存部分を変更すれば、他の環境でも使えるはずです(std::_Xbad_alloc()、_NOEXCEPT、_THROW0など)。
追記2017/9/6
VisualStuio2017更新で_THROW0が廃止されました。_THROW0を削除するか定義しないとコンパイルエラーになります。その他のVisualStudio固有の定義(_NOEXCEPTなど)は、独自の定義に変更してください。

//STL用メモリ管理
void* Malloc(size_t size);
void Free(void* ptr, size_t size);//size:メモリの確保サイズ、Mallocのsize

// <xmemory0> allocatorを改造

// STL用自作アロケータ
template<class _Ty>
class Allocator
{  // generic allocator for objects of class _Ty
public:
    typedef Allocator<_Ty> other;

    typedef _Ty value_type;
    typedef value_type *pointer;
    typedef const value_type *const_pointer;
    typedef void *void_pointer;
    typedef const void *const_void_pointer;

    typedef value_type& reference;
    typedef const value_type& const_reference;

    typedef size_t size_type;
    typedef ptrdiff_t difference_type;

    template<class _Other>
    struct rebind
    {	// convert this type to allocator<_Other>
        typedef Allocator<_Other> other;
    };

    pointer address(reference _Val) const _NOEXCEPT
    {	// return address of mutable _Val
        return (_STD addressof(_Val));
    }

    const_pointer address(const_reference _Val) const _NOEXCEPT
    {	// return address of nonmutable _Val
        return (_STD addressof(_Val));
    }

    Allocator() _THROW0()
    {	// construct default allocator (do nothing)
    }

    Allocator(const Allocator<_Ty>&) _THROW0()
    {	// construct by copying (do nothing)
    }

    template<class _Other>
    Allocator(const Allocator<_Other>&) _THROW0()
    {	// construct from a related allocator (do nothing)
    }

    template<class _Other>
    Allocator<_Ty>& operator=(const Allocator<_Other>&)
    {	// assign from a related allocator (do nothing)
        return (*this);
    }

    void deallocate(pointer _Ptr, size_type _Count)
    {	// deallocate object at _Ptr
        Free(_Ptr, _Count*sizeof (_Ty));
    }

    pointer allocate(size_type _Count)
    {	// allocate array of _Count elements
        void *_Ptr = 0;

        if (_Count == 0)
			;
        else if (((size_t)(-1) / sizeof (_Ty) < _Count)
        || (_Ptr = Malloc(_Count * sizeof (_Ty))) == 0)
            std::_Xbad_alloc();	// report no memory

        return ((_Ty *)_Ptr);
    }

    pointer allocate(size_type _Count, const void *)
    {	// allocate array of _Count elements, ignore hint
        return (allocate(_Count));
    }

    void construct(_Ty *_Ptr)
    {	// default construct object at _Ptr
        ::new ((void *)_Ptr) _Ty();
    }

    void construct(_Ty *_Ptr, const _Ty& _Val)
    {	// construct object at _Ptr with value _Val
        ::new ((void *)_Ptr) _Ty(_Val);
    }

    template<class _Uty>
    void destroy(_Uty *_Ptr)
    {	// destroy object at _Ptr
        _Ptr->~_Uty();
    }

    size_t max_size() const _THROW0()
    {	// estimate maximum array size
        return ((size_t)(-1) / sizeof (_Ty));
    }
};

// Allocatorが同じものかどうか
template<class _Ty, class _Other> inline
bool operator==(const Allocator<_Ty>& _Left,
                const Allocator<_Other>& _Right) _THROW0()
{   // test for allocator equality
    return sizeof(_Ty)==sizeof(_Other);
    //サイズが同じであれば、2つのアロケータは同じもの
    //もしMalloc,Free関数ではなく::operator new deleteを使用した場合
    //どの型でも同じアロケータになるため常にreturn true;
}

template<class _Ty,
class _Other> inline
bool operator!=(const Allocator<_Ty>& _Left,
                const Allocator<_Other>& _Right) _THROW0()
{   // test for allocator inequality
    return !(Left==Right);
}

自作アロケータの使用例

// vector
template <class TYPE>
struct Vector
{
    typedef std::vector<TYPE, Allocator<TYPE>> T;
};
// 使用例
Vector<int>::T v;

// string
typedef std::basic_string<char, std::char_traits<char>,Allocator<char>> String;
typedef std::basic_string<wchar_t, std::char_traits<wchar_t>,Allocator<wchar_t>> WString;

// map
template <class KEY, class TYPE, class PR = std::less<KEY>>
struct Map
{
    typedef std::map<KEY, TYPE, PR, Allocator<std::pair<const KEY, TYPE>>> T;
};

//VS2012(Update2)では未対応のエイリアステンプレートでの定義
template <TYPE>
using Vector = std::vector<TYPE, Allocator<TYPE>>;

メモリ使用量を監視するためのメモリ確保解放関数の例

namespace{
// ATOMIC操作はInterlocked???を使用(Windows専用)
// あくまでデバッグ用、中途半端なスレッド対応
// 完全にスレッドセーフにするなら関数全体をmutexなどで排他処理
// そこまでする必要がないので(mutexとか遅いし、効率が悪いので)変数のATOMIC操作のみ
volatile long  sizeMalloc = 0;
volatile long countMalloc = 0;
}//ns

//STL用メモリ管理
void* Malloc(size_t size)
{
    void* ptr = ::operator new(size);
    if(ptr){
        InterlockedIncrement(&countMalloc);
        InterlockedExchangeAdd(&sizeMalloc, (long)size);
    }
    return ptr;
}
void Free(void* ptr, size_t size)
{
    ::operator delete(ptr);
    if(ptr){
        InterlockedDecrement(&countMalloc);
        InterlockedExchangeAdd(&sizeMalloc, -(long)size);
    }
}

アロケータの比較演算子operator==について

アロケータが同じものかどうか⇒片方のアロケータで確保したメモリをもう片方のアロケータで解放できるかどうか
という解釈で作成しています。STLの仕様に完全に対応しているかどうかわかりません。とりあえず問題なく動作しているだけかもしれないので、利用の際はご注意ください。
vectorやstringのソースコードを見てみると、コピーやムーブする際にアロケータを入れ替えるかどうかの判定で使っているようです。ムーブ時にアロケータが異なる場合、コピーと同じ動作になるため効率への影響が大きくなります。

コメントを残す

メールアドレスが公開されることはありません。

認証:数字を入力してください(必須) * Time limit is exhausted. Please reload CAPTCHA.