86.1. std::allocator を模したカスタムアロケーター

 アロケーターの基本は std::allocator とするのは良いとして、カスタムアロケーターを作る場合は割り当てをカスタマイゼーションしなきゃだめです。

 割り当てが普通なら新しいアロケーターを作る必要もないですからね。

 ですがいきなり新しいものを作るのはハードルが高すぎないですかね?

 てなことで std::allocator っぽいものを作ってみましょう。

 といっても std::allocator と同等のもを作るのは難しいので最小構成のアロケーターを目標にします。

  1 #include <memory>
  2 #include <limits>
  3 #include <iostream>
  4
  5 template<typename T, typename Allocator = std::allocator<T>>
  6 class A {
  7 public:
  8   typedef T value_type;
  9   typedef T* pointer;
 10   typedef const T* const_pointer;
 11   typedef T& reference;
 12   typedef const T& const_reference;
 13   typedef std::size_t size_type;
 14   typedef std::ptrdiff_t difference_type;
 15   typedef const void* const_void_pointer;
 16   typedef Allocator allocator_type;
 17
 18   explicit A(const allocator_type& alloc = allocator_type())
 19     : M_allocator_(alloc)
 20   {}
 21
 22   template<typename U>
 23   A(const A<U,Allocator>& other)
 24     : M_allocator_(other.M_allocator_)
 25   {}
 26
 27   template<typename U>
 28   struct rebind {
 29     typedef A<U> other;
 30   };
 31
 32   pointer allocate(size_type n, const_void_pointer cvp = const_void_pointer());
 33   void deallocate(pointer p, size_type n);
 34   size_type max_size();
 35
 36   template<typename U, typename... Args>
 37   void construct(U* p, Args&&... args);
 38
 39   template<typename U>
 40   void destroy(U* p);
 41
 42   pointer address(reference r) const noexcept;
 43   const_pointer address(const_reference cr) const noexcept;
 44
 45 private:
 46   allocator_type M_allocator_;
 47 };
 48
 49 template<typename T1, std::size_t N, typename T2>
 50 bool operator==(const A<T1>& , const A<T2>& )
 51 {
 52   return true; //内部データをもたないため == はすべて等しいとみなす
 53 }
 54
 55 template<typename T1, std::size_t N, typename T2>
 56 bool operator!=(const A<T1>& , const A<T2>& )
 57 {
 58   return false; //内部データをもたないため != は全て異なるとみなす
 59 }
 60
 61 template<typename T, typename Allocator>
 62 typename A<T,Allocator>::pointer A<T,Allocator>::allocate(
 63     size_type n,
 64     const_void_pointer )
 65 {
 66   return reinterpret_cast<pointer>(::operator new(n*sizeof(T)));
 67 }
 68
 69 template<typename T, typename Allocator>
 70 void A<T,Allocator>::deallocate(
 71     pointer p,
 72     size_type )
 73 {
 74   ::operator delete(p);
 75 }
 76
 77 template<typename T, typename Allocator>
 78 typename A<T,Allocator>::size_type A<T,Allocator>::max_size()
 79 {
 80   return std::numeric_limits<T>::max();
 81 }
 82
 83 template<typename T, typename Allocator>
 84 template<typename U, typename ... Args>
 85 void A<T,Allocator>::construct(U* p, Args&&... args)
 86 {
 87   ::new ((void*)p) U(std::forward<Args>(args)...);
 88 }
 89
 90 template<typename T, typename Allocator>
 91 template<typename U>
 92 void A<T,Allocator>::destroy(U* p)
 93 {
 94   p->~U();
 95 }
 96
 97 template<typename T, typename Allocator>
 98 typename A<T,Allocator>::pointer A<T,Allocator>::address(reference r) const noexcept
 99 {
100   return std::addressof(r);
101 }
102
103 template<typename T, typename Allocator>
104 typename A<T,Allocator>::const_pointer A<T,Allocator>::address(const_reference cr) const noexcept
105 {
106   return std::addressof(cr);
107 }
108
109 int main()
110 {
111   A<int> a;
112   int* d = a.allocate(8);
113   a.construct(d,5);
114   std::cout << "d = " << d << " *d = " << *d << '\n';
115   std::cout << "address = " << a.address(*d) << '\n';
116   std::cout << "max_size = " << a.max_size() << '\n';
117   a.destroy(d);
118   a.deallocate(d,8);
119   return 0;
120 }

ビルドと実行結果. 

$ g++ main.cpp
$ ./a.out
d = 0x564da7df9e70 *d = 5
address = 0x564da7df9e70
max_size = 2147483647

 まず std::allocator っぽいインターフェースを作るには、テンプレート引数に T 型を割り当てるデータ型にして Allocator には std::allocator<T> をデフォルトにしておきます。

 ただこの実装は最小構成なので Allocator とデフォルト引数の std::allocator は空のインターフェースで使うことはないです。

  5 template<typename T, typename Allocator = std::allocator<T>>
  6 class A {
  7 public:
  8   typedef T value_type;
  9   typedef T* pointer;
 10   typedef const T* const_pointer;
 11   typedef T& reference;
 12   typedef const T& const_reference;
 13   typedef std::size_t size_type;
 14   typedef std::ptrdiff_t difference_type;
 15   typedef const void* const_void_pointer;
 16   typedef Allocator allocator_type;

 組み込み型・エイリアスは決まりごとなので特に考える必要ないので、大体変わることはないですし、独自色のある実装を入れないほうが良いでしょう。

 次にメンバーオブジェクトには Allocator 型をエイリアスにした allocator_type 型のものを用意しておきます。

 45 private:
 46   allocator_type M_allocator_;

 このソースでは M_allocator_ を使うことはないですが、他のアロケーターへの切り替えを持つために設計として残しておくのはありだと思います。

 もし必要ないと思うなら以下のパラメーター初期化の箇所を消すだけで大丈夫です。

 18   explicit A(const allocator_type& alloc = allocator_type())
 19     : M_allocator_(alloc)
 20   {}
 21
 22   template<typename U>
 23   A(const A<U,Allocator>& other)
 24     : M_allocator_(other.M_allocator_)
 25   {}

 new の他に aligned_alloc() も使うとか、改善点はあるので、このクラスを拡張するのに使ってみると良い練習になるかもしれないですね。

Copyright 2018-2019, by Masaki Komatsu