第86章 std::allocator を使うだけのリファレンスアロケーター

目次

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

 C++ の開発者が通常使うのは std::allocator です。

 汎用アロケーターなので使いどころ満載なアロケーターですが、本書では実装の確認のために使いたいと思います。

 実装確認とは std::allocator をベースとしたリファレンス実装を持つことで、カスタムアロケーターを作った際に正しい挙動確認のことです。

 後はカスタムアロケーターが適用できないデータへのフォールバック用(バックアップ)アロケーターとして std::allocator を使うことも考えられるので単なる確認以上の意味もあります。

 では std::allocator をフルに活用してインターフェースを実装してみましょう。

main.cpp. 

  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 typename std::allocator_traits<Allocator>::value_type value_type;
  9   typedef typename std::allocator_traits<Allocator>::pointer pointer;
 10   typedef typename std::allocator_traits<Allocator>::const_pointer const_pointer;
 11   typedef typename Allocator::reference reference;
 12   typedef typename Allocator::const_reference const_reference;
 13   typedef typename std::allocator_traits<Allocator>::size_type size_type;
 14   typedef typename std::allocator_traits<Allocator>::difference_type difference_type;
 15   typedef typename std::allocator_traits<Allocator>::const_void_pointer 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 cvp)
 65 {
 66   return M_allocator_.allocate(n,cvp);
 67 }
 68
 69 template<typename T, typename Allocator>
 70 void A<T,Allocator>::deallocate(
 71     pointer p,
 72     size_type n)
 73 {
 74   return M_allocator_.deallocate(p,n);
 75 }
 76
 77 template<typename T, typename Allocator>
 78 typename A<T,Allocator>::size_type A<T,Allocator>::max_size()
 79 {
 80   return M_allocator_.max_size();
 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   M_allocator_.construct(p,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   M_allocator_.destroy(p);
 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 M_allocator_.address(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 M_allocator_.address(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 = 0x555759601e70 *d = 5
address = 0x555759601e70
max_size = 4611686018427387903

 まずはメモリーアドレスの割り当て部分です。

 61 template<typename T, typename Allocator>
 62 typename A<T,Allocator>::pointer A<T,Allocator>::allocate(
 63     size_type n,
 64     const_void_pointer cvp)
 65 {
 66   return M_allocator_.allocate(n,cvp);
 67 }
 68
 69 template<typename T, typename Allocator>
 70 void A<T,Allocator>::deallocate(
 71     pointer p,
 72     size_type n)
 73 {
 74   return M_allocator_.deallocate(p,n);
 75 }

 このように単純に std::allocator 型の M_allocator_ オブジェクトのメンバー関数を呼び出しています。

 allocate 関数は割当で deallocate 関数は解放を行ってくれます。

111   A<int> a;
112   int* d = a.allocate(8);
118   a.deallocate(d,8);

 allocate は new となり、 deallocate は delete として機能します。

 なぜなら std::allocator はヒープ領域から割当てをするからです。

 次にアロケーターによるデータの構成と破壊の部分です。

 83 template<typename T, typename Allocator>
 84 template<typename U, typename ... Args>
 85 void A<T,Allocator>::construct(U* p, Args&&... args)
 86 {
 87   M_allocator_.construct(p,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   M_allocator_.destroy(p);
 95 }

 これは以下のように使われています。

113   a.construct(d,5);
117   a.destroy(d);

 construct の第一引数は割り当て済みのアドレス、第二引数は値です。

 これを直接使うと効率の悪いイメージでしょうが STL のデータの設定や削除の際に呼ばれたりします。

 construct と destroy はバックグラウンド処理として考えれば必要性がわかるんじゃないかなと思います。

 max_size関数は STL コンテナに組み込まれたサイズ関数を呼び出してるだけです。

Copyright 2018-2019, by Masaki Komatsu