一般的にオブジェクト指向プログラミングにおいて、機能やデータはクラスで分けた方が後々、変更が楽りますし再利用性もが上がります。
複数のアロケーターインスタンスを作ったほうが、長期的にはメンテがしやすいってことです。
では前の例を少し変更して考えて見ましょう。
まずスタックアロケーターを作るという意味ですと、メモリープールは 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