アロケータークラス内での複数の割り当て方針を切り替えですが、メモリープールを複数のブロックサイズで持っておくと簡単に作れます。
例えば以下のようなクラスです。
13 class allocator
14 {
15 public:
16
17 allocator()
18 :
19 M_max_objects_(MAX_OBJECTS)
20 {
21 Mp_pool_[0] = ::new (&Mp_Mem8_) std::byte[sizeof(std::byte[8])*MAX_OBJECTS];
22 Mp_pool_[1] = ::new (&Mp_Mem16_) std::byte[sizeof(std::byte[16])*MAX_OBJECTS];
23 Mp_pool_[2] = ::new (&Mp_Mem32_) std::byte[sizeof(std::byte[32])*MAX_OBJECTS];
24 Mp_pool_[3] = ::new (&Mp_Mem64_) std::byte[sizeof(std::byte[64])*MAX_OBJECTS];
25 for(size_t i = 0; i < MAX_ALLOCS; ++i){
26 M_pool_index_[i] = 0;
27 M_block_in_use_[i] = 0;
28 M_block_size_[i] = sizeof(std::byte[8 << i]); //8,16,32,64を代入
29 }
30 }
//中略
107 std::byte* Mp_pool_[MAX_ALLOCS];
//中略
110 std::byte Mp_Mem8_[sizeof(std::byte[8])*MAX_OBJECTS];
111 std::byte Mp_Mem16_[sizeof(std::byte[16])*MAX_OBJECTS];
112 std::byte Mp_Mem32_[sizeof(std::byte[32])*MAX_OBJECTS];
113 std::byte Mp_Mem64_[sizeof(std::byte[64])*MAX_OBJECTS];
114 };Mp_pool_ がメモリープールとなり、各メモリープールのサイズはストレージ変数で持つことになります。
例えば Mp_mem8_ は 8 バイト均等のブロックサイズで、 Mp_mem16_ は 16 バイト均等のブロックサイズの記憶領域となります。
後は placement new (配置 new )でメモリープールにアドレスを代入していってますね。
21 Mp_pool_[0] = ::new (&Mp_Mem8_) std::byte[sizeof(std::byte[8])*MAX_OBJECTS]; 22 Mp_pool_[1] = ::new (&Mp_Mem16_) std::byte[sizeof(std::byte[16])*MAX_OBJECTS]; 23 Mp_pool_[2] = ::new (&Mp_Mem32_) std::byte[sizeof(std::byte[32])*MAX_OBJECTS]; 24 Mp_pool_[3] = ::new (&Mp_Mem64_) std::byte[sizeof(std::byte[64])*MAX_OBJECTS];
こんな感じで、メモリープールが 4 個併存するような設計にして、それをクラスの内部実装で切り替えますが、詳細はフルのソースコードで解説したいと思います。
main.cpp.
1 #include <cstddef>
2 #include <memory>
3 #include <cassert>
4 #include <new>
5 #include <cstdlib>
6 #include <iostream>
7 #include <cstring>
8 #include <list>
9
10 #define MAX_OBJECTS 32
11 #define MAX_ALLOCS 4
12
13 template<typename T>
14 class allocator
15 {
16 public:
17
18 allocator()
19 :
20 M_max_objects_(MAX_OBJECTS)
21 {
22 Mp_pool_[0] = ::new (&Mp_Mem8_) std::byte[sizeof(std::byte[8])*MAX_OBJECTS];
23 Mp_pool_[1] = ::new (&Mp_Mem16_) std::byte[sizeof(std::byte[16])*MAX_OBJECTS];
24 Mp_pool_[2] = ::new (&Mp_Mem32_) std::byte[sizeof(std::byte[32])*MAX_OBJECTS];
25 Mp_pool_[3] = ::new (&Mp_Mem64_) std::byte[sizeof(std::byte[64])*MAX_OBJECTS];
26 for(size_t i = 0; i < MAX_ALLOCS; ++i){
27 M_pool_index_[i] = 0;
28 M_block_in_use_[i] = 0;
29 M_block_size_[i] = sizeof(std::byte[8 << i]);
30 }
31 }
32
33 ~allocator(){}
34
35 inline void* allocate(size_t n, size_t size){
36 if(n > 1){
37 return std::malloc(n*size);
38 }
39 size_t index;
40 for(int i = 0; i < MAX_ALLOCS; ++i)
41 if(size <= 8)
42 index = 0;
43 else if(size <= 16)
44 index = 1;
45 else if(size <= 32)
46 index = 2;
47 else
48 index = 3;
49
50 void* pblock = pop_from_pool(index);
51 if(!pblock)
52 {
53 if(M_pool_index_[index] < M_max_objects_)
54 {
55 ++M_pool_index_[index];
56 std::cout << "M_pool_index : M_block_in_use_ = " << M_pool_index_[index]
57 << " : " << M_block_in_use_[index] << '\n';
58 pblock = static_cast<void*>(Mp_pool_[index] + (M_pool_index_[index] * M_block_size_[index]));
59
60 } else {
61 std::cout << "M_pool_index = " << M_pool_index_[index] << '\n';
62 assert(false);
63 }
64 ++M_block_in_use_[index];
65 std::cout << "object size / block size = " << size << "/" << M_block_size_[index] << '\n';
66 }
67 return pblock;
68 }
69
70 inline void deallocate(void* memory, size_t n, size_t size)
71 {
72 size_t index;
73 if(n > 1){
74 std::free(memory);
75 return;
76 }
77 for(int i = 0; i < MAX_ALLOCS; ++i)
78 if(size <= 8)
79 index = 0;
80 else if(size <= 16)
81 index = 1;
82 else if(size <= 32)
83 index = 2;
84 else
85 index = 3;
86
87 rewind(memory,index);
88 }
89
90 private:
91 // 開放(deallocate)時に使用可能領域を更新(再割り当て)
92 void rewind(void* memory, size_t pos){
93 block_not_in_use* pblock = static_cast<block_not_in_use*>(memory);
94 pblock->next = M_head_[pos]; //先頭ポインタを次にすすめる
95 M_head_[pos] = pblock;
96 }
97 // 割当(allocate)時に開放された使用可能領域から使う
98 void* pop_from_pool(size_t pos){
99 block_not_in_use* pblock = nullptr;
100 if(M_head_[pos])
101 {
102 pblock = M_head_[pos];
103 M_head_[pos] = M_head_[pos]->next;
104 }
105 return static_cast<void*>(pblock);
106 }
107 // 使用可能なメモリータグ(一度割当、後に開放された領域)
108 struct block_not_in_use {
109 block_not_in_use *next;
110 };
111 size_t M_block_size_[MAX_ALLOCS];
112 const size_t M_max_objects_;
113 block_not_in_use* M_head_[MAX_ALLOCS];
114 std::byte* Mp_pool_[MAX_ALLOCS];
115 unsigned M_pool_index_[MAX_ALLOCS]; //新規割当時にインクリメント、下がることはない
116 unsigned M_block_in_use_[MAX_ALLOCS];
117 std::byte Mp_Mem8_[sizeof(std::byte[8])*MAX_OBJECTS];
118 std::byte Mp_Mem16_[sizeof(std::byte[16])*MAX_OBJECTS];
119 std::byte Mp_Mem32_[sizeof(std::byte[32])*MAX_OBJECTS];
120 std::byte Mp_Mem64_[sizeof(std::byte[64])*MAX_OBJECTS];
121 };
122
123 /* スタックメモリーのブロックを割り当てるクラステンプレート */
124
125 template<typename T>
126 class block_allocator
127 {
128 public:
129 typedef T* pointer;
130 typedef const T* const_pointer;
131 typedef T value_type;
132
133 template<typename U>
134 struct rebind
135 {
136 typedef block_allocator<U> other;
137 };
138
139 explicit block_allocator() noexcept {
140 }
141 ~block_allocator() {
142 }
143
144 template<typename U>
145 block_allocator(const block_allocator<U>&){}
146
147 template<typename U>
148 block_allocator(const std::allocator<U>&){}
149
150 T* allocate(std::size_t n)
151 {
152 return reinterpret_cast<T*>(S_alloc_.allocate(n,sizeof(T)));
153 }
154
155 void deallocate(T* p,size_t n)
156 {
157 S_alloc_.deallocate(static_cast<void*>(p),n,sizeof(T));
158 }
159
160 private:
161 static allocator<T> S_alloc_;
162 };
163
164 template<typename T>
165 allocator<T> block_allocator<T>::S_alloc_;
166
167 /* voidパラメーターのエラーチェックのためにvoid型のクラステンプレートを用意 */
168
169 template <>
170 class block_allocator<void>
171 {
172 public:
173 typedef void* pointer;
174 typedef const void* const_pointer;
175 typedef void value_type;
176
177 template<typename U>
178 struct rebind
179 {
180 typedef block_allocator<U> other;
181 };
182
183 explicit block_allocator() noexcept {
184 }
185
186 template<typename U>
187 block_allocator(const block_allocator<U>&){}
188
189 template<typename U>
190 block_allocator(std::allocator<U>&){}
191
192 };
193
194 /* カスタムアロケータ― */
195
196 template<typename Allocator, typename U>
197 struct block_rebind
198 {
199 typedef typename Allocator::template rebind<U>::other type;
200 };
201
202 /* std::allocator テンプレート(使用しない)*/
203
204 template<typename T, typename U>
205 struct block_rebind<std::allocator<T>,U>
206 {
207 typedef block_allocator<U> type;
208 };
209
210 /* カスタムアロケータ―型のエイリアス */
211
212 template<typename Allocator, typename U>
213 using block_rebind_t = typename block_rebind<Allocator,U>::type;
214
215 /* std::unique_ptr の引数に使う deleter */
216
217 template<typename Allocator, typename T>
218 class block_delete
219 {
220 public:
221 explicit block_delete(const Allocator& alloc) noexcept
222 : M_alloc_(alloc)
223 {
224 }
225 template<typename Other,typename U>
226 block_delete(const block_delete<Other,U>& d) noexcept
227 : M_alloc_(d.M_alloc_)
228 {}
229 void operator()(T* p) const
230 {
231 p->~T();
232 M_alloc_.deallocate(p,1);
233 }
234 private:
235 mutable block_rebind_t<Allocator,T> M_alloc_;
236 };
237
238 /* カスタムアロケータ―で割り当てるスマートポインターの型 */
239
240 template<typename Allocator, typename T>
241 using block_ptr = std::unique_ptr<T,block_delete<Allocator,T>>;
242
243 /* 割当関数 */
244
245 template<typename T, typename Allocator, typename... Args>
246 block_ptr<Allocator,T> allocate_block(const Allocator& alloc, Args&&... args)
247 {
248 block_rebind_t<Allocator,T> rebound_alloc(alloc);
249 T* praw = rebound_alloc.allocate(1);
250 try {
251 T* p = new (praw) T(std::forward<Args>(args)...);
252 return block_ptr<Allocator,T>(p,block_delete<Allocator,T>(rebound_alloc));
253 } catch (...) {
254 rebound_alloc.deallocate(praw,1);
255 }
256 }
257
258 struct A{
259 A(int x, int y, int z, int a) :
260 x_(x),
261 y_(y),
262 z_(z),
263 a_(a)
264 {}
265 int x_,y_,z_,a_;
266 };
267
268 int main()
269 {
270 using int_allocator = block_allocator<int>;
271 using a_allocator = block_allocator<A>;
272
273 int_allocator alloc;
274 int_allocator alloc_copy;
275 a_allocator alloc_a;
276
277 auto in1 = allocate_block<int,int_allocator>(alloc,0);
278 auto in2 = allocate_block<A,a_allocator>(alloc_a,0,0,0,0);
279
280 for(int i = 0; i < 4; ++i) {
281 {
282 in1 = allocate_block<int,block_allocator<int>>(alloc,100*i);
283 }
284 auto l = allocate_block<int,int_allocator>(alloc,200);
285 auto m = allocate_block<int,int_allocator>(alloc,300);
286 auto n = allocate_block<int,int_allocator>(alloc,400);
287 auto o = allocate_block<int,int_allocator>(alloc,500);
288 // alloc_copy は alloc と同じ allocator を共有しています
289 // アロケーターをスタティックにするためにおきる古くから知られた古典的な問題です
290 auto a = allocate_block<int,int_allocator>(alloc_copy,600);
291 auto b = allocate_block<int,int_allocator>(alloc_copy,700);
292 auto c = allocate_block<int,int_allocator>(alloc_copy,800);
293 {
294 auto f = allocate_block<A,a_allocator>(alloc_a,1,2,3,4);
295 auto g = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
296 in2 = allocate_block<A,a_allocator>(alloc_a,9*i,10*i,11*i,12*i);
297 }
298 auto v = allocate_block<A,a_allocator>(alloc_a,5,6,7,8);
299 auto w = allocate_block<A,a_allocator>(alloc_a,15,16,17,18);
300 auto s = allocate_block<A,a_allocator>(alloc_a,20,21,22,23);
301 auto t = allocate_block<A,a_allocator>(alloc_a,25,26,27,28);
302 std::cout << "in1 : " << *(in1.get()) << '\n';
303 std::cout << "in2 : " << in2.get()->x_ << '\n';
304 }
305 }
ビルドと実行結果.
$ g++ main.cpp -std=c++17 $ ./a.out M_pool_index : M_block_in_use_ = 1 : 0 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 1 : 0 object size / block size = 16/16 M_pool_index : M_block_in_use_ = 2 : 1 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 3 : 2 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 4 : 3 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 5 : 4 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 6 : 5 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 7 : 6 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 8 : 7 object size / block size = 4/8 M_pool_index : M_block_in_use_ = 2 : 1 object size / block size = 16/16 M_pool_index : M_block_in_use_ = 3 : 2 object size / block size = 16/16 M_pool_index : M_block_in_use_ = 4 : 3 object size / block size = 16/16 M_pool_index : M_block_in_use_ = 5 : 4 object size / block size = 16/16 in1 : 0 in2 : 0 in1 : 100 in2 : 9 in1 : 200 in2 : 18 in1 : 300 in2 : 27
このソースコードには以下のようなクラスがあります。
メインのロジックを実装しているのが class allocator となります。
block_allocator クラスはマネージャークラスという聞き慣れない表現だと思いますが、allocator オブジェクトを保持する以外の機能と、アロケーター要件を満たす機能ぐらいしかなく、中身は基本的に空っぽです。
注目すべき点としては、以下のようにアロケーターのスタティックインスタンスを持ってることです。
118 template<typename T>
119 class block_allocator
120 {
//中略
153 private:
154 static allocator S_alloc_;
155 };
156
157 template<typename T>
158 allocator block_allocator<T>::S_alloc_;S_alloc_ はポインターでなく実体オブジェクトのために強くバインドされてしまいますが、アロケーターは一つという場合では支障はないですかね。
S_alloc_ は allocate() と deallocate() 関数の実装に使います。
143 T* allocate(std::size_t n)
144 {
145 return reinterpret_cast<T*>(S_alloc_.allocate(n));
146 }
147
148 void deallocate(T* p,size_t n)
149 {
150 S_alloc_.deallocate(static_cast<void*>(p),n);
151 }block_delete クラステンプレートや allocate_block() 関数については慣用表現とまでは言いませんが、スマートポインターを作るためのヘルパークラスと関数と考えてもらえばと思います。
allocate_block() の詳細については前の章を参照してください。
Copyright 2018-2019, by Masaki Komatsu