将所申请的整块内存划分为同等尺寸的若干内存块并以链表形式串联在一起,每当响应内存申请时则从链表中取出内存块返回,除非链表为空时才向系统申请;响应内存释放时则将内存块插入链表即可。这种缓冲固定尺寸内存块的管理方式称为对象池
池分配器模板类的设计
- 为了提高内存块的使用率,对象池一般为多实例共享同一缓冲区。也就是说,对象池应该声明为池分配器的静态成员。
- 但问题在于,不同模板实例之间的静态成员不同,也就是说
pool_allocator<int*>
与pool_allocator<char*>
并不共用一个对象池。 - 为了实现跨类型的内存共享,我们可以将对象池声明为一个非模板来的静态成员,在声明池分配器模板时以该类为基类。这样,多个分配器模板实例都拥有同一基类,从而共享同一内存池。也就是说:
class pool_allocator_base{
static pool_type_pool();
//...
};
template<typename T>
class pool_allocator : pool_allocator_base{
//...
};
对象池的实现
- 对象池中的空闲内存块以链表形式串联,这个链表称为空闲表(free list)
- 为了减少向系统申请内存的次数,对象池总是一次申请多个内存快的空间。这种从系统申请得到的内存空间叫做组块(chunk)
- 组块的信息也需要保存以便之后向系统释放,所以也可以用一个链表来保存,这个叫做组块表(chunk list)
虽然组块一旦申请好之后便立即分割成若干空闲块加入空闲表,但是之后随着对象池不断响应申请以及释放请求,空闲块的顺序将与组块顺序不一致,这给释放组块内存带来一定的困难
要释放一个组块,需要先确保其分配的所有空闲块都在空闲表中,并从响应的空闲表中删除这些空闲块。如果没有附加信息,根本无从确定某个空闲块是属于哪个组块。只有在内存池析构时才能确定所有组块都不再使用而安全释放。 也就是说,需要增加一些额外信息,来快速定位空闲块所属组块并快速确定组块是否所有空闲都空闲。
首先,同一组块所分配的内存块都是相邻的,那么,为内存块额外保留一个序号就可以快速定位到组块地址:
// 组块类型
struct free_node{
counter_type bias; // 该内存块在组块中的序号
free_node* next; // 指向下一个内存块
};
对于空闲块尺寸为block_size字节的空闲表,其free_node实际上应有的内存尺寸为:free_node_size = offsetof(free_node, next) + std::max(sizeof(free_node*), node_size)
。
#include <stdexcept>
//预定义每个chunk所含的内存块数,可由用户修改
#ifndef MYLIB_NUM_NODES_PER_CHUNK
#define MYLIB_NUM_NODES_PER_CHUNK 128
#endif
namespace mylib{
class object_pool{
static const unsigned char num_nodes_per_chunk = MYLIB_NUM_NODES_PER_CHUNK;
typedef unsigned char counter_type;
struct chunk_date{
counter_type free_node_count;
chunk_date *next;
};
static const size_t chunk_data_size = sizeof(chunk_date);
chunk_date* chunk_data_head;
// 组块类型
struct free_node{
counter_type bias;
free_node* next;
};
static const size_t free_node_offset = offsetof(free_node, next);
free_node* free_node_head;
const size_t node_size = 0;
const size_t free_node_size = 0;
const size_t chunk_size = 0;
public:
object_pool(size_t node_size) :
chunk_data_head(nullptr),
free_node_head(nullptr),
node_size(node_size),
free_node_size(free_node_offset + std::max(sizeof(free_node*), node_size)),
chunk_size(chunk_data_size + free_node_size * num_nodes_per_chunk)
{}
~object_pool(){
// 释放时需要释放所有已经申请的内存
while (chunk_data_head){
chunk_date* chunk = chunk_data_head;
chunk_data_head = chunk_data_head->next;
operator delete(chunk);
}
}
// 分配以及回收空间的函数
void *allocator() noexcept(false);
void deallocate(void *ptr);
size_t recycle(); // 释放空闲组块
};
// 可自动构造不同尺寸内存池的数组内
class object_pool_array{
size_t sz;
object_pool *array;
public:
// sz为数组中对象池的个数,step为各对象池尺寸只差
// 比如sz=4,step=8时将构造4的对象池,尺寸依次为8,16,32,40
object_pool_array(size_t sz, size_t step) :
sz(sz),
// 先为array申请空间,但不构造内存池
array(static_cast<object_pool*>(operator new(sizeof(object_pool)*sz))){
// 按照步长递增尺寸构造对象池
for(size_t i = 0; i < sz; i++){
new (array + i) object_pool(i * step + step);
}
}
~object_pool_array(){
// 先依次析构对象池
for(size_t i = 0; i < sz; i++){
(array + i)->~object_pool();
}
// 释放内存
operator delete(array);
}
size_t size() const {return sz;}
object_pool& operator[] (size_t n) {return array[n];}
};
};