Fantacity

Stand Alone Complex

STL allocator(1)

最近看侯捷侯老师的《STL源码剖析》一书(下称STL书),在此对书中内容做个整理,加深印象。stl的六个主要部件分别为:allocator(空间配置器),iterators(迭代器),containers(容器),algorithms(算法),functors(仿函数)以及adapters(适配器)。这里先对allocator做一下总结。

概览

由于STL实现版本之多,因此STL书主要以SGI STL这一实现为源码来分析。SGI STL中吧allocator定义于之中,有三个文件,分别是:

#include <stl_alloc.h> // 负责内存空间的配置与释放
#include <stl_construct.h> // 负责对象内容的构造与析构
#include <stl_uninitialized.h> // 定义了一些全局函数,用来填充或复制大块内存数据

那么为什么要把内存分配以及对象的初始化(或者对象的析构和内存的释放)这两个步骤分离开来,直接使用new表达式不好吗?我的理解是这样的:是为了能提升效率

首先从内存分配来说,我们有时会遇到只需要申请内存,但并不需要(或者暂时不需要)初始化的情况,比如下文会提到的sgi alloc的两级配置,其中第一级专门负责配置小块的内存(<128Bytes),如果小块的内存直接向系统申请,那么时间一长很容易造成内存碎片,使得内存无法得到高效利用。

alloc的做法是先向系统申请一块大的内存池,然后再通过内存池向stl分配小块的内存,而大块的内存依旧使用普通的分配方式。直观地理解,这么做提高了内存的使用效率。

在来看对象析构方面,有些对象在析构的时候需要做一些额外的事情,比如有一个文件对象,那么我们在析构的时候还需要关闭文件句柄。但是有些对象在析构的时候其实什么事情都不需要做,这个时候就没必要调用对象的析构函数,直接释放内存就好了。所以sgi把对象的析构和内存的释放这两步分离开来,通过判断has_non_trivial_destructor来判断是否需要调用对象的析构函数,以提高效率。

来看一张关于这3个头文件分别做了什么的图
headers

construct和destroy

首先来看一下的部分内容。该文件内主要定义了两类全局模板函数:construct()函数和destroy()函数,分别用于对象的构造和析构。下图是对construct和destroy的一个整体概览。
constructor()和destroy()
constructor有一个泛化的版本。对于destroy有一个泛化的版本以及对两个字符串指针char* wchar_t*和一个原生指针类型T*的特化版本。而泛型版本又根据是否有non-trivial destructor特化出了两个版本。看一下源码。
code
code
那么为什么要这么做呢,主要是从效率考虑的,首先对于字符串类型,我们不需要调用析构函数,对于原生指针类型,直接调用其析构函数就可以了。
然后再来看剩下的情况,对于接受两个迭代器类型的版本,根据value_type()Traits(萃取)出它的类型T,再使用__type_traits<T>::has_trivial_destructor萃取出该类是否含有non-trivial destructor,如果有,那么需要依次调用destructor,如果只含有trivial destructor,那么什么都不用做。