STL Allocator空间分配器二
今天把SGI提供的Allocator分配器仔细看了下,其设计还是相当精巧的。不过SGI的分配器已经脱离了STL标准,比如它就没有实现construct()和destroy()成员函数。
一 简单malloc分配器
对于大于128B的空间直接就是malloc()和free()了,没有什么特殊的;不过它还是仿造C++的new handler形式设置了一个malloc exception handler;这样就和C++的new行为相一致了,你可以设置malloc失败时的exception handler。
二 New和delete的分离
当对一个对象调用new和delete时,这两个操作都包含了两个阶段的动作:调用operator new分配空间,然后调用该对象的构造函数;调用该对象的析构函数,然后调用operator delete释放空间;SGI的分配器将这两步做了分离,内存分配和释放由函数alloc::allocate()和alloc::deallocate()负责;物体构造和析构由函数construct()和destroy()负责。
在调用destroy()函数同时释放n个对象(假设类型为T)时,SGI提供了方法可以判定对象是否有non-trivial destructor,如果没有则不必要循环为每个对象调用T::~T(),以提高效率,贴上源码,以便查看:
template <class T1, class T2>
inline void construct(T1* p, const T2& value) {
new (p) T1(value);
}
template <class T>
inline void destroy(T* pointer) {
pointer->~T();
}
template <class ForwardIterator>
inline void // 具有non trivial destructor
__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) {
for ( ; first < last; ++first)
destroy(&*first);
}
template <class ForwardIterator>
inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {} //空函数体,trivial destructor不需要调用
template <class ForwardIterator, class T>
inline void __destroy(ForwardIterator first, ForwardIterator last, T*) {
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
__destroy_aux(first, last, trivial_destructor());
}
template <class ForwardIterator>
inline void destroy(ForwardIterator first, ForwardIterator last) {
__destroy(first, last, value_type(first));
}
这需要借助于Traits编程技法来完成(原书3.7节)的:
typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
首先使用value_type()获取迭代器指向的物体类型,然后使用__type_traits<T>查看T是否有non-trivial destructor。
三 简单分配器 simple_alloc
SGI为这原始分配器malloc和次级分配器alloc所作的一层简单封装;
四 次级分配器alloc
对于小于128B的请求采用了次级分配器;说白了就是SGI维护一个内存池来处理这些请求,以保证效率。它会将请求的字节数n圆整到8的倍数,比如如果请求的是14B的空间,其实获得的是16B;从这也可以得出SGI一共有16个链表需要维护,每个链表对应一个分配级别,对应的内存块大小分别是:8、16、24、…、128。
为了不浪费存储空间,链表是一个union结构,像这样:
union OBJ{
union OBJ *free;
char *data[1];
};
其free指针指向的是free链表中下一个空闲内存块,如果一个内存块被分配出去,就交给用户程序维护了,那么该块就没有必要继续维护了,知道再次收回(通过 free())。
分配和回收函数allocate()和deallocate()就是简单的链表操作了,没有特别的地方。
比较复杂的就是内存池的维护函数chunk_alloc()函数,它最终还是需要通过malloc()来申请内存新的空间。
五 辅助函数
最后是几个操作为初始化空间的辅助函数,其内部基本都是通过调用全局construct()函数完成的。
六 对别人不是问题的问题
读到最后一直有个疑惑就是allocator没有实现construct()函数,那么它和容器是如何协同工作的呢?搜了搜源文件才发现,原来各容器都会显式调用全局函数construct()(construct函数实际调用placement new),比如下面是list容器模板的一段代码:
typedef simple_alloc<list_node, Alloc> list_node_allocator;
link_type get_node() { return list_node_allocator::allocate(); }
void put_node(link_type p) { list_node_allocator::deallocate(p); }
link_type create_node(const T& x) {
link_type p = get_node();
__STL_TRY {
construct(&p->data, x);
}
__STL_UNWIND(put_node(p));
return p;
}
void destroy_node(link_type p) {
destroy(&p->data);
put_node(p);
}