96.1. 複数のアロケーターインスタンスを切り替え

一般的にオブジェクト指向プログラミングにおいて、機能やデータはクラスで分けた方が後々、変更が楽りますし再利用性もが上がります。

複数のアロケーターインスタンスを作ったほうが、長期的にはメンテがしやすいってことです。

では前の例を少し変更して考えて見ましょう。

まずスタックアロケーターを作るという意味ですと、メモリープールは 1 次元で十分となります。

それとストレージ用の配列も 1 個あれば十分です。

すると以下のような allocator クラスに変更することができます。

 12 template<std::size_t N>
 13 class allocator
 14 {
 15 public:
 16   typedef std::size_t size_type;
 17
 18   explicit inline allocator()
 19     :
 20     M_block_size_(sizeof(std::byte[N])),
 21     M_max_objects_(MAX_OBJECTS),
 22     M_head_(nullptr),
 23     M_pool_index_(0)
 24   {
 25     Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
 26   }

 // 中略

 57
 58 private:

 // 中略

 82   std::byte* Mp_pool_;
 83   unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
 84   std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
 85 };

Mp_pool_ は配列ではないですし Mp_mem_ も一つ分しかないですね。

後はテンプレート引数 N が Mp_mem_ の記憶域のサイズを定義しています。

次にマネージャークラス block_allocator です。

 89 template<typename T>
 90 class block_allocator
 91 {

// 中略

169 private:
170   static allocator(1) S_alloc8_;
171   static allocator(2) S_alloc16_;
172   static allocator(3) S_alloc32_;
173   static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5)  block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;

このようにマネージャークラスにはアロケータークラス 4 個分のスタティックインスタンスを宣言・定義しておきます。

S_alloc8_ が 8 バイト均等ブロックの割り当て、S_alloc16_ が 16 バイト均等ブロックの割り当て、 S_alloc32_ が 32 バイト均等のブロックの割り当てを行います。

注意したいのは block_allocator<T> で(テンプレート引数の違う)インスタンスが 1 個増えるたびに 4 個分のアロケーターインスタンスが作られることですかね。

まあアロケーターのインスタンスはサイズからすりゃ大したことないんですけど。

ではフルソースで実装と検証をしてみましょう。

main.cpp. 

  1 #include <cstddef>
  2 #include <memory>
  3 #include <cassert>
  4 #include <cstdlib>
  5 #include <iostream>
  6 #include <list>
  7 #include <vector>
  8
  9 #define MAX_OBJECTS 32
 10 #define MAX_ALLOCS 4
 11
 12 template<std::size_t N>
 13 class allocator
 14 {
 15 public:
 16   typedef std::size_t size_type;
 17
 18   explicit inline allocator()
 19     :
 20     M_block_size_(sizeof(std::byte[N])),
 21     M_max_objects_(MAX_OBJECTS),
 22     M_head_(nullptr),
 23     M_pool_index_(0)
 24   {
 25     Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
 26   }
 27
 28   inline ~allocator(){
 29   }
 30
 31   inline void* allocate(){
 32     void* pblock = pop_from_pool();
 33     if(!pblock)
 34     {
 35       if(M_pool_index_ < M_max_objects_)
 36       {
 37         ++M_pool_index_;
 38         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
 39         std::cout << M_block_size_ << ": M_pool_index=" << M_pool_index_  << " pblock=" << pblock << '\n';
 40       } else {
 41         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 42         assert(false);
 43       }
 44     }
 45     return pblock;
 46   }
 47
 48   inline void deallocate(void* memory)
 49   {
 50     std::cout << "rewind allocated address: " << memory << '\n';
 51     rewind(memory);
 52   }
 53
 54   inline size_t get_block_size() const {
 55     return M_block_size_;
 56   }
 57
 58 private:
 59   // 開放(deallocate)時に使用可能領域を更新
 60   inline void rewind(void* memory){
 61     block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
 62     pblock->next = M_head_; //先頭ポインタを新規ポインタの next ポインタに設定
 63     M_head_ = pblock; //新たなブロックを先頭ポインタに設定
 64   }
 65   // 割当(allocate)時に開放された使用可能領域から使う
 66   inline void* pop_from_pool(){
 67     block_not_in_use* pblock = nullptr;
 68     if(M_head_)
 69     {
 70       pblock = M_head_;
 71       M_head_ = M_head_->next;
 72     }
 73     return static_cast<void*>(pblock);
 74   }
 75   // 使用可能なメモリータグ(一度割当、後に開放された領域)
 76   struct block_not_in_use {
 77     block_not_in_use *next;
 78   };
 79   const size_t M_block_size_;
 80   const unsigned M_max_objects_;
 81   block_not_in_use* M_head_;
 82   std::byte* Mp_pool_;
 83   unsigned M_pool_index_; //新規割当時にインクリメント、下がることはない
 84   std::byte Mp_Mem_[sizeof(std::byte[N])*MAX_OBJECTS];
 85 };
 86
 87 /* スタックメモリーのブロックを割り当てるクラステンプレート */
 88
 89 template<typename T>
 90 class block_allocator
 91 {
 92 public:
 93   typedef T value_type;
 94   typedef value_type* pointer;
 95   typedef const value_type* const_pointer;
 96   typedef value_type& reference;
 97   typedef const value_type& const_reference;
 98   typedef std::size_t size_type;
 99   typedef std::ptrdiff_t difference_type;
100
101   template<typename U>
102   struct rebind
103   {
104     typedef block_allocator<U> other;
105   };
106
107   inline block_allocator() noexcept
108   {}
109   inline ~block_allocator() {}
110
111   inline explicit block_allocator(const block_allocator&) noexcept {}
112
113   template<typename U>
114   inline constexpr block_allocator(const block_allocator<U>&) noexcept
115   {}
116
117   inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118   {
119     auto s = sizeof(T) * n;
120     if(s <= 8)
121       return reinterpret_cast<T*>(S_alloc8_.allocate());
122     else if(s <= 16)
123       return reinterpret_cast<T*>(S_alloc16_.allocate());
124     else if(s <= 32)
125       return reinterpret_cast<T*>(S_alloc32_.allocate());
126     else if(s <= 64)
127       return reinterpret_cast<T*>(S_alloc64_.allocate());
128     else
129       assert(false);
130   }
131
132   inline void deallocate(T* p, size_type n) noexcept
133   {
134     auto s = sizeof(T) * n;
135     if(s <= 8)
136       S_alloc8_.deallocate(reinterpret_cast<void*>(p));
137     else if(s <= 16)
138       S_alloc16_.deallocate(reinterpret_cast<void*>(p));
139     else if(s <= 32)
140       S_alloc32_.deallocate(reinterpret_cast<void*>(p));
141     else if(s <= 64)
142       S_alloc64_.deallocate(reinterpret_cast<void*>(p));
143     else
144       assert(false);
145   }
146
147   inline size_type max_size() const
148   {
149     return std::numeric_limits<size_type>::max();
150   }
151
152   static size_type capacity()
153   {
154     return MAX_OBJECTS;
155   }
156
157   template<typename U, typename ... Args>
158   void construct(U* p, Args&&... args) {
159     ::new ((void*)p) T(args...);
160   }
161
162   template<typename U>
163   void destroy(U* p)
164   {
165     std::cout << "destroy: " << p << '\n';
166     p->~U();
167   }
168
169 private:
170   static allocator(1) S_alloc8_;
171   static allocator(2) S_alloc16_;
172   static allocator(3) S_alloc32_;
173   static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5)  block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;
187
188 /* voidパラメーターのエラーチェックのためにvoid型のクラステンプレートを用意 */
189
190 template <>
191 class block_allocator<void>
192 {
193 public:
194   typedef void* pointer;
195   typedef const void* const_pointer;
196   typedef void value_type;
197
198   template<typename U>
199   struct rebind
200   {
201     typedef block_allocator<U> other;
202   };
203
204   explicit block_allocator() noexcept {
205   }
206
207   template<typename U>
208   block_allocator(const block_allocator<U>&){}
209
210   template<typename U>
211   block_allocator(std::allocator<U>&){}
212
213 };
214
215 template<typename T>
216 using rebind_t = typename block_allocator<T>::template rebind<T>::type;
217
218 template<typename T, typename T2>
219 inline bool operator==(block_allocator<T> const&, block_allocator<T2> const&)
220 {
221   return true;
222 }
223
224 template<typename T, typename OtherAllocator>
225 inline bool operator==(block_allocator<T> const&, OtherAllocator const&)
226 {
227   return false;
228 }
229
230 /* カスタムアロケータ― */
231
232 template<typename Allocator, typename U>
233 struct block_rebind
234 {
235   typedef typename Allocator::template rebind<U>::other type;
236 };
237
238 /* カスタムアロケータ―型のエイリアス */
239
240 template<typename Allocator, typename U>
241 using block_rebind_t = typename block_rebind<Allocator,U>::type;
242
243 /* std::unique_ptr の引数に使う deleter */
244
245 template<typename Allocator, typename T>
246 class block_delete
247 {
248 public:
249   explicit block_delete(const Allocator& alloc) noexcept
250     : M_alloc_(alloc)
251   {
252   }
253   template<typename Other,typename U>
254   block_delete(const block_delete<Other,U>& d) noexcept
255     : M_alloc_(d.M_alloc_)
256   {}
257   void operator()(T* p) const
258   {
259     p->~T();
260     M_alloc_.deallocate(p,1);
261   }
262 private:
263   mutable block_rebind_t<Allocator,T> M_alloc_;
264 };
265
266 /* カスタムアロケータ―で割り当てるスマートポインターの型 */
267
268 template<typename Allocator, typename T>
269 using block_ptr = std::unique_ptr<T,block_delete<Allocator,T>>;
270
271 /* 割当関数 */
272
273 template<typename T, typename Allocator, typename... Args>
274 block_ptr<Allocator,T> allocate_block(const Allocator& alloc, Args&&... args)
275 {
276   block_rebind_t<Allocator,T> rebound_alloc(alloc);
277   T* praw = rebound_alloc.allocate(1);
278   try {
279     T* p = new (praw) T(std::forward<Args>(args)...);
280     return block_ptr<Allocator,T>(p,block_delete<Allocator,T>(rebound_alloc));
281   } catch (...) {
282     rebound_alloc.deallocate(praw,1);
283   }
284 }
285
286 struct A{
287   A(int x, int y, int z, int a) :
288     x_(x),
289     y_(y),
290     z_(z),
291     a_(a)
292   {}
293   int x_,y_,z_,a_;
294
295 };
296
297 int main()
298 {
299   using int_allocator = block_allocator<int>;
300   using a_allocator = block_allocator<A>;
301
302   int_allocator alloc;
303   int_allocator alloc_copy;
304   a_allocator alloc_a;
305
306   for(int i = 0; i < 2; ++i) {
307     auto l = allocate_block<int,int_allocator>(alloc,200);
308     // alloc_copy は alloc と同じ allocator を共有しています
309     auto a = allocate_block<int,int_allocator>(alloc_copy,600);
310     {
311       auto f = allocate_block<A,a_allocator>(alloc_a,1,2,3,4);
312       auto g = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
313     }
314     auto v = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
315     auto w = allocate_block<A,a_allocator>(alloc_a,15,16,17,18);
316   }
317
318   return 0;
319 }

ビルドと実行結果. 

$ g++ selector.cpp -std=c++17
$ ./a.out
8: M_pool_index=1 pblock=0x555a4579816c
8: M_pool_index=2 pblock=0x555a45798174
16: M_pool_index=1 pblock=0x555a457992b4
16: M_pool_index=2 pblock=0x555a457992c4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a45798174
rewind allocated address: 0x555a4579816c
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a45798174
rewind allocated address: 0x555a4579816c

このセレクターはフリーリストを回避するための実装方法としての意味もあります。

まずは block_allocator クラステンプレートを見てみましょう。

 87 /* スタックメモリーのブロックを割り当てるクラステンプレート */
 88
 89 template<typename T>
 90 class block_allocator
 91 {
 92 public:
 93   typedef T value_type;
 94   typedef value_type* pointer;
 95   typedef const value_type* const_pointer;
 96   typedef value_type& reference;
 97   typedef const value_type& const_reference;
 98   typedef std::size_t size_type;
 99   typedef std::ptrdiff_t difference_type;
100
101   template<typename U>
102   struct rebind
103   {
104     typedef block_allocator<U> other;
105   };
106
107   inline block_allocator() noexcept
108   {}
109   inline ~block_allocator() {}
110
111   inline explicit block_allocator(const block_allocator&) noexcept {}
112
113   template<typename U>
114   inline constexpr block_allocator(const block_allocator<U>&) noexcept
115   {}
116
117   inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118   {
119     auto s = sizeof(T) * n;
120     if(s <= 8)
121       return reinterpret_cast<T*>(S_alloc8_.allocate());
122     else if(s <= 16)
123       return reinterpret_cast<T*>(S_alloc16_.allocate());
124     else if(s <= 32)
125       return reinterpret_cast<T*>(S_alloc32_.allocate());
126     else if(s <= 64)
127       return reinterpret_cast<T*>(S_alloc64_.allocate());
128     else
129       assert(false);
130   }
131
132   inline void deallocate(T* p, size_type n) noexcept
133   {
134     auto s = sizeof(T) * n;
135     if(s <= 8)
136       S_alloc8_.deallocate(reinterpret_cast<void*>(p));
137     else if(s <= 16)
138       S_alloc16_.deallocate(reinterpret_cast<void*>(p));
139     else if(s <= 32)
140       S_alloc32_.deallocate(reinterpret_cast<void*>(p));
141     else if(s <= 64)
142       S_alloc64_.deallocate(reinterpret_cast<void*>(p));
143     else
144       assert(false);
145   }
146
147   inline size_type max_size() const
148   {
149     return std::numeric_limits<size_type>::max();
150   }
151
152   static size_type capacity()
153   {
154     return MAX_OBJECTS;
155   }
156
157   template<typename U, typename ... Args>
158   void construct(U* p, Args&&... args) {
159     ::new ((void*)p) T(args...);
160   }
161
162   template<typename U>
163   void destroy(U* p)
164   {
165     std::cout << "destroy: " << p << '\n';
166     p->~U();
167   }
168
169 private:
170   static allocator(1) S_alloc8_;
171   static allocator(2) S_alloc16_;
172   static allocator(3) S_alloc32_;
173   static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5)  block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;

まあこれだけで巨大なんですが重要な箇所は allocate() 関数だけです。

117   inline pointer allocate(size_type n, typename std::allocator<T>::const_pointer = 0)
118   {
119     auto s = sizeof(T) * n;
120     if(s <= 8)
121       return reinterpret_cast<T*>(S_alloc8_.allocate());
122     else if(s <= 16)
123       return reinterpret_cast<T*>(S_alloc16_.allocate());
124     else if(s <= 32)
125       return reinterpret_cast<T*>(S_alloc32_.allocate());
126     else if(s <= 64)
127       return reinterpret_cast<T*>(S_alloc64_.allocate());
128     else
129       assert(false);
130   }

sizeof(T) * n のサイズを一つのブロックサイズとして、各ブロックサイズのアロケーターへのセレクター機能を allocate() 関数が担当しています。

例えば 8 バイトブロックの 3 個分がリクエストされたら、リクエストサイズは 16 バイトと 32 バイトの間なので S_alloc32_ の allocate() をコールします。

16 バイトブロック 2 個分のリクエストサイズは 32 バイトになるので、 S_alloc32_ の allocate() をコールします。

16 バイトブロック 3 個分のリクエストサイズは 48 バイトになるので、 S_alloc64_ の allocate() が呼ばれます。

まあこんな感じで block_allocator の allocate() 関数が固定サイズアロケーターをセレクトしていきます。

この実装の利点は allocator クラステンプレート側に型名パラメーター T が存在しないことです。

  9 #define MAX_OBJECTS 32
 10 #define MAX_ALLOCS 4
 11
 12 template<std::size_t N>
 13 class allocator
 14 {
 15 public:
 16   typedef std::size_t size_type;
 17
 18   explicit inline allocator()
 19     :
 20     M_block_size_(sizeof(std::byte[N])),
 21     M_max_objects_(MAX_OBJECTS),
 22     M_head_(nullptr),
 23     M_pool_index_(0)
 24   {
 25     Mp_pool_ = new (&Mp_Mem_) std::byte[sizeof(std::byte[N])*MAX_OBJECTS];
 26   }

つまり allocator はブロックサイズとリクエストサイズが等しいという前提で設計ができるので、リクエストサイズとフリーチャンクの情報のメンテナンスは不要になります。

 28   inline ~allocator(){
 29   }
 30
 31   inline void* allocate(){
 32     void* pblock = pop_from_pool();
 33     if(!pblock)
 34     {
 35       if(M_pool_index_ < M_max_objects_)
 36       {
 37         ++M_pool_index_;
 38         pblock = static_cast<void*>(Mp_pool_ + (M_pool_index_ * M_block_size_));
 39         std::cout << M_block_size_ << ": M_pool_index=" << M_pool_index_  << " pblock=" << pblock << '\n';
 40       } else {
 41         std::cout << "M_pool_index = " << M_pool_index_ << '\n';
 42         assert(false);
 43       }
 44     }
 45     return pblock;
 46   }
 47
 48   inline void deallocate(void* memory)
 49   {
 50     std::cout << "rewind allocated address: " << memory << '\n';
 51     rewind(memory);
 52   }

このようにスタックアロケーターのロジックをかなり省略できたことにお気づきになれますかね?

もちろん呼び出し関数もよりシンプルになってます。

 59   // 開放(deallocate)時に使用可能領域を更新
 60   inline void rewind(void* memory){
 61     block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
 62     pblock->next = M_head_; //先頭ポインタを新規ポインタの next ポインタに設定
 63     M_head_ = pblock; //新たなブロックを先頭ポインタに設定
 64   }
 65   // 割当(allocate)時に開放された使用可能領域から使う
 66   inline void* pop_from_pool(){
 67     block_not_in_use* pblock = nullptr;
 68     if(M_head_)
 69     {
 70       pblock = M_head_;
 71       M_head_ = M_head_->next;
 72     }
 73     return static_cast<void*>(pblock);
 74   }
 75   // 使用可能なメモリータグ(一度割当、後に開放された領域)
 76   struct block_not_in_use {
 77     block_not_in_use *next;
 78   };

rewind() は単にフリー(チャンク)リストの先頭を解放したフリーチャンクに設定し、前の先頭は next ポインターに設定されます。

pop_from_pool() も先頭からフリーチャンクを取り出して返すだけですね。

一旦説明を block_allocator クラステンプレートに戻したいと思います。

170   static allocator(1) S_alloc8_;
171   static allocator(2) S_alloc16_;
172   static allocator(3) S_alloc32_;
173   static allocator(4) S_alloc64_;
174 };
175
176 template<typename T>
177 allocator(5)  block_allocator<T>::S_alloc8_;
178
179 template<typename T>
180 allocator(6) block_allocator<T>::S_alloc16_;
181
182 template<typename T>
183 allocator(7) block_allocator<T>::S_alloc32_;
184
185 template<typename T>
186 allocator(8) block_allocator<T>::S_alloc64_;

このケースだとアロケーターは 4 個しかないですが、スタティックインスタンスをいくつも用意しておくのであれば 20 - 30 個分ぐらいあっても大丈夫です。

これスタックだから心配(ガクブル)とお考えになるかもしれないですが、大丈夫ですよ。

スタティックインスタンスが割り当てられるのはスタック領域じゃなくヒープ領域に割り当てられる(実装依存)という前提で考えれば良いと思います。

そもそもメモリーが足りないというよりフラグメンテーションが怖いケースの方が多いので、多少メモリー容量を無駄にしてでも実装する価値はあります。

テストコードについては、スマートポインター化するための allocate_block 関数テンプレートを使います。

230 /* カスタムアロケータ― */
231
232 template<typename Allocator, typename U>
233 struct block_rebind
234 {
235   typedef typename Allocator::template rebind<U>::other type;
236 };
237
238 /* カスタムアロケータ―型のエイリアス */
239
240 template<typename Allocator, typename U>
241 using block_rebind_t = typename block_rebind<Allocator,U>::type;
242
243 /* std::unique_ptr の引数に使う deleter */
244
245 template<typename Allocator, typename T>
246 class block_delete
247 {
248 public:
249   explicit block_delete(const Allocator& alloc) noexcept
250     : M_alloc_(alloc)
251   {
252   }
253   template<typename Other,typename U>
254   block_delete(const block_delete<Other,U>& d) noexcept
255     : M_alloc_(d.M_alloc_)
256   {}
257   void operator()(T* p) const
258   {
259     p->~T();
260     M_alloc_.deallocate(p,1);
261   }
262 private:
263   mutable block_rebind_t<Allocator,T> M_alloc_;
264 };
265
266 /* カスタムアロケータ―で割り当てるスマートポインターの型 */
267
268 template<typename Allocator, typename T>
269 using block_ptr = std::unique_ptr<T,block_delete<Allocator,T>>;
270
271 /* 割当関数 */
272
273 template<typename T, typename Allocator, typename... Args>
274 block_ptr<Allocator,T> allocate_block(const Allocator& alloc, Args&&... args)
275 {
276   block_rebind_t<Allocator,T> rebound_alloc(alloc);
277   T* praw = rebound_alloc.allocate(1);
278   try {
279     T* p = new (praw) T(std::forward<Args>(args)...);
280     return block_ptr<Allocator,T>(p,block_delete<Allocator,T>(rebound_alloc));
281   } catch (...) {
282     rebound_alloc.deallocate(praw,1);
283   }
284 }

カスタムデリーターを実装して std::unique_ptr のインスタンスを作っているだけです。

ただリクエスト個数が 1 個に限定されているので、ここは引数を増やして対応するのもありっちゃありそうですが T 型のオブジェクトを複数個欲しいというのは STL コンテナが行う最適化なので STL コンテナを使わないのであれば、これで十分だと思います。

ではテストコードを見てみましょう。

286 struct A{
287   A(int x, int y, int z, int a) :
288     x_(x),
289     y_(y),
290     z_(z),
291     a_(a)
292   {}
293   int x_,y_,z_,a_;
294
295 };
296
297 int main()
298 {
299   using int_allocator = block_allocator<int>;
300   using a_allocator = block_allocator<A>;
301
302   int_allocator alloc;
303   int_allocator alloc_copy;
304   a_allocator alloc_a;
305
306   for(int i = 0; i < 2; ++i) {
307     auto l = allocate_block<int,int_allocator>(alloc,200);
308     // alloc_copy は alloc と同じ allocator を共有しています
309     auto a = allocate_block<int,int_allocator>(alloc_copy,600);
310     {
311       auto f = allocate_block<A,a_allocator>(alloc_a,1,2,3,4);
312       auto g = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
313     }
314     auto v = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
315     auto w = allocate_block<A,a_allocator>(alloc_a,15,16,17,18);
316   }
317
318   return 0;
319 }

2 回 int 型を生成し、4 回 A 型のオブジェクトを生成していますね。

さらに A 型オブジェクトについては 2 回はブロックの内部にあるので有効期限が瞬時に切れて解放されます。

ついでに int 型オブジェクトもループが終了するたびに解放されます。

そのため以下のように新規割り当ては 2 回だけで、その後は既に割り当てたアドレスを再利用していきます。

8: M_pool_index=1 pblock=0x555a4579816c
8: M_pool_index=2 pblock=0x555a45798174
16: M_pool_index=1 pblock=0x555a457992b4
16: M_pool_index=2 pblock=0x555a457992c4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a45798174
rewind allocated address: 0x555a4579816c
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a457992c4
rewind allocated address: 0x555a457992b4
rewind allocated address: 0x555a45798174
rewind allocated address: 0x555a4579816c

これでセレクターについての説明は終わりですが、実装面に慣れてきたら、最終的にはメモリーをどう使いたいのかという設計の問題にいきつくでしょうね。

Copyright 2018-2019, by Masaki Komatsu