allocatorの仕組み

単純なアロケータの実装

template <class T>
struct mini_allocator {
  typedef T value_type;

  T* allocate(size_t n) {
    return reinterpret_cast<T*>(new char[n*sizeof(T)]);
  }

  void deallocate(T* p, size_t n) {
    delete[] reinterpret_case<char*>(p);
  }
};

バイト単位(char型)でメモリ領域を確保・開放するだけのもの。 実際にはnewdeleteである必要はなく、スタック領域に確保したものを返す実装でも構わないことになる。 スタックが巨大なマシンであれば、ヒープ領域から確保せずにスタック領域だけで済ませることも可能なのか気になる。 多分できる。

rebind

アロケータはテンプレート引数によって確保したい型を指定する。 そのため、別の型でメモリ領域を確保したいときは別のアロケータを新たに定義する必要がある。

あるアロケータの正しい実装obj_allocator<T>があり、Fooクラスを確保するアロケータFoo_Alloc = obj_allocator<Foo>を定義する。 Barクラスに対するアロケータの定義を行いたい時にrebindを使うことができる。

rebindは以下のように定義されている。

template <class T>
class allocator {
  // ...

 public:
  template <class U>
  struct rebind {
    typedef allocator<U> other;
  };

  // ...
};

rebindを使うと、obj_allocator<T>を複数箇所で使うことなく、同じアロケータで複数の型に対するアロケータを作成することができる。

typedef obj_allocator<Foo> foo_allocator_type;
typedef typename foo_allocator_type::template rebind<Bar>::other bar_allocator_type;

アロケータの実装を別のものに変えたいときは、foo_allocator_typeの定義1つを変えれば良い。

感想

rebindについてあまり理解できていなかったなと思った。 C++のテンプレートは便利だけど、考えることが多すぎるのと抽象的な部分が多くて難しい。 C++コンパイル時Cコンパイラみたいな面白挙動ができるのも納得。

追記

テンプレート引数でアロケータの型が渡される時にrebindが活躍する。

template <class T, class Allocator>
class Foo;

を使う場合は、

Foo<int, std::allocator<int> > val;

のように宣言する。

Fooクラスの内部実装からはTのアロケータであることしかわからず、アロケータ自体がどのような実装かまでは判別できない。 std::allocatorかもしれないし、mini_allocatorかもしれない。 rebindを使うことで、アロケータの実装が取り出せる。 つまり、同じアロケータを使って別の型のアロケータを定義できる。

参考文献

プログラミング言語C++ 第4版 34.4 アロケータ