最近看侯捷侯老师的《STL源码剖析》一书(下称STL书),在此对书中内容做个整理,加深印象。stl的六个主要部件分别为:allocator(空间配置器),iterators(迭代器),containers(容器),algorithms(算法),functors(仿函数)以及adapters(适配器)。这里先对allocator做一下总结。
概览
由于STL实现版本之多,因此STL书主要以SGI STL这一实现为源码来分析。SGI STL中吧allocator定义于
那么为什么要把内存分配以及对象的初始化(或者对象的析构和内存的释放)这两个步骤分离开来,直接使用new表达式不好吗?我的理解是这样的:是为了能提升效率。
首先从内存分配来说,我们有时会遇到只需要申请内存,但并不需要(或者暂时不需要)初始化的情况,比如下文会提到的sgi alloc的两级配置,其中第一级专门负责配置小块的内存(<128Bytes),如果小块的内存直接向系统申请,那么时间一长很容易造成内存碎片,使得内存无法得到高效利用。
alloc的做法是先向系统申请一块大的内存池,然后再通过内存池向stl分配小块的内存,而大块的内存依旧使用普通的分配方式。直观地理解,这么做提高了内存的使用效率。
在来看对象析构方面,有些对象在析构的时候需要做一些额外的事情,比如有一个文件对象,那么我们在析构的时候还需要关闭文件句柄。但是有些对象在析构的时候其实什么事情都不需要做,这个时候就没必要调用对象的析构函数,直接释放内存就好了。所以sgi把对象的析构和内存的释放这两步分离开来,通过判断has_non_trivial_destructor来判断是否需要调用对象的析构函数,以提高效率。
来看一张关于这3个头文件分别做了什么的图
construct和destroy
首先来看一下
constructor有一个泛化的版本。对于destroy有一个泛化的版本以及对两个字符串指针char*
wchar_t*
和一个原生指针类型T*
的特化版本。而泛型版本又根据是否有non-trivial destructor特化出了两个版本。看一下源码。
那么为什么要这么做呢,主要是从效率考虑的,首先对于字符串类型,我们不需要调用析构函数,对于原生指针类型,直接调用其析构函数就可以了。
然后再来看剩下的情况,对于接受两个迭代器类型的版本,根据value_type()
Traits(萃取)出它的类型T,再使用__type_traits<T>::has_trivial_destructor
萃取出该类是否含有non-trivial destructor
,如果有,那么需要依次调用destructor,如果只含有trivial destructor,那么什么都不用做。