11. 自定义allocator的合理用法

(本节内容并未完全掌握,建议学习STL源码剖析相关章节后再次复习本章)

何时需要自定义allocator?

 
如果你对STL中默认的allocator感到不满,因为:

  1. 你发现它太慢、浪费内存或造成过度的碎片
  2. 本次编写的程序无需考虑多线程,没必要支付同步开销
  3. 某些容器里的对象通常一同被使用,所以需要在一个特别的堆里把它们放得很近,使引用的区域性最大化
  4. 需要建立一个相当于共享内存的唯一的堆,然后把一个或多个容器放在那块内存里,使其他进程得以共享

那你需要一种自定义allocator的方案(包括但不限定于上述几种情况)。


问题实例

建立一个相当于共享内存的堆

假定我们有一些特殊过程,采用malloc与free内存模型来管理一个位于共享内存的堆,并且需要把STL容器的内容放置于共享内存:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void* mallocShared(size_t bytesNeeded);
void freeShared(void *ptr);
...
template<typename T>
class SharedMemoryAllocator {
public:
...
pointer allocate(size_type numObiects, const void *localityHint = 0){
return static_cast<pointer>(mallocShared(numObiects * sizeof(T)));
}
void deallocate(pointer ptrToMemory, size_ type numObjects){
freeShared(ptrToMiemory);
}
...
};

SharedMemoryAllocator的使用方式如下:
1
2
3
4
5
6
typedef vector<double, SharedMemoryAllocator<double> > SharedDoubleVec;
...
{ // 开始某一个块
SharedDoubleVec v; //v在共享内存中
... // 结束这个块
}

但值得注意的是:v使用了自定义allocator作为分配器,因此所分配的容纳元素的内存必然来自共享内存。但是v自己,包括其所有数据成员,必然不会出现在共享内存中,它们存在于栈内。
为了把v的内容与v自身一起放入共享内存,有操作如下:
1
2
3
4
5
void *pVectorMemory =  mallocShared(sizeof(SharedDoubleVec));// 分配内存容纳SharedDoubleVec对象
SharedDoubleVec *pv = new (pVectorMemory) SharedDoubleVec;//placement new 操作详见Effective C++ 52
...//使用pv
pv->~SharedDoubleVec(); //销毁共享内存中的容器
freeShared(pVectorMemory); // 销毁共享内存块

总地来说,使用步骤依次为:
分配➡️构造➡️析构➡️释放

聚合容器内的元素

假设我们有Heap1与Heap2两个堆,每个堆都有相应的静态成员函数来执行内存分配和释放操作:

1
2
3
4
5
6
7
8
class Heap1 {
public:
...
static void* alloc(size_t numBytes, const void *memoryBlockToBeNear);
static void dealloc(void *ptr);
...
};
class Heap2 { ... }; //接口与Heap1类似

进一步地,我们想自定义容器元素容纳于哪一个堆。因此,我们必须设定一个分配器,使用像heap1,heap2那样真实管理内存的类:
1
2
3
4
5
6
7
8
9
10
11
template<typenameT, typename Heap>
class SpecificHeapAllocator {
public:
pointer allocate(size_type numObjects, const void *localityHint = 0){
return static_cast<pointer>(Heap::alloc(numObjects * sizeof(T),localityHint));
}
void deallocate(pointer ptrToMemory, size_type numObjects){
Heap::dealloc(ptrToMemory);
}
...
};

然后再使用allocator把容器的元素集合到一起:
1
2
3
4
vector<int, SpecificHeapAllocator<int,Heap1> > v;
set<int, SpecificHeapAllocator<int,Heap1> > s;
list<Widget,SpecificHeapAllocator<Widget,Heap2> > L;
map<int, string, less<int>,SpecificHeapAllocator<pair<const int, string>,Heap2> > m;

值得注意的是,Heap1和Heap2是类型而不是对象。如果Heap1和Heap2是对象而不是类型,那么它们将是不等价的分配器,那就违反了分配器的等价约束。