SGI STL空间配置器(STL源码剖析)

SGI STL空间配置器(STL源码剖析)

空间配置器的标准接口(根据STL规范)

 

allocator::value_type
allocator::pointer
allocator::const_pointer
allocator::reference
allocator::const_reference
allocator::size_type
allocator::difference_type
allocator::rebind 
// 一个嵌套的类模板

allocator::allocator()
allocator::allocator(
const  allocator & )
template
< class  U >  allocator::allocator( const  allocator < U >& // 泛化的拷贝构造函数 
allocator:: ~ allocator()

pointer allocator::address(reference x) 
const
// 返回某个对象的地址.  a.address(x) 等于 &x

const_pointer allocator::address(const_reference x) 
const
// 同上. 返回一个const对象的地址

pointer allocator::allocate(size_type n, 
const   void *   =   0  )
// 分配空间, 足以存储n个T对象

void  allocator::deallocate(pointer p, size_type n)
// 释放空间

size_type allocator::max_size() 
const
// 返回可成功分配的最大量

void  allocator::construct(pointer p ,  const  T &  x)
// 负责构造 相当于 new ((const void*)p) T(x)

void  allocator::destroy(pointer p)
// 负责析构 相当于 p->~T()

 

——————————————————————————————————————

SGI STL 的配置器与众不同, 名称是alloc而不是allocator, 而且不接受任何参数。

 

vector < int  , std::allocator < int >   >  iv;    // in VC or CB

vector
< int  , std::alloc  >  iv;                 // in GCC

 

但是通常都是使用默认的空间配置器,而SGI STL已经为每一个容器都指定了缺省的空间配置器。所以使用的时候无太大区别。

 

template < class  T,  class  Alloc  =  alloc >

class  vector { } ;        // 缺省使用alloc

 

————————————————————————————————————————

SGI空间配置器分析:

C++的new操作符和delete操作符进行内存配置时,new:先配置内存,然后构造对象。delete:先析构对象,然后释放内存。SGI STL将内存配置、释放内存与构造、析构分开。前者由<stl_alloc.h>中的allocate()和deallocate()负责,后者由<stl_construct.h>中的construct()和destroy()负责。 这两个头文件包含于<memory>中(STL标准规定,配置器定义在memory中)。同时<memory>还包含了一个文件<stl_unitialized.h>,定义了一些全局函数用来填充、复制大块内存数据。

 

1.构造和析构。

构造:

 

template < class  T1,  class  T2 >

inline 
void  construct(T1  * p,  const  T2 &  value) {

new (p) T1(value);   //此处用到placement new 运算,将初始值设定到指针P所指的地方 


PS:placement new运算,并不分配内存,而是将已分配的内存调用构造函数来进行初始化。

析构:

析构函数第一个版本接受一个指针,将指针所指之物删除,可以直接调用析构函数。而第二个版本,接受两个迭代器,将其之间的所有对象全部析构。由于我们不知道这个范围的大小,有时候范围很大而每个对象的析构不是必要的,就会造成浪费。因此需要进行一个判断,根据得到的结果来判断是否需要析构它(判断用到了traits方法)。

下面是最终的析构调用情况,其中__false_type和__ture_type是两个空的struct,只用于标识,使其通过函数重载在编译的时候确定调用哪一个版本。

 

template < class  ForwardIterator >

__destroy_aux(ForwardIterator first, ForwardIterator last, __false_type)

{

for(; first<last; ++first)

destroy(
&*first);

}


template
< class  ForwardIterator >  

__destroy_aux(ForwardIterator first, ForwardIterator last, __ture_type)

{}

 

 同时这个析构函数对char*和wchar_t*有特化版本:将不调用他们的析构函数。(这是没有必要调用析构函数的例子么?)

2. 空间的配置和释放: std::alloc

std::alloc为了效率,设计了双层级配置器,第一级直接使用C的malloc()和free()。第二级则视配置区块的大小选择配置器,如果区块大,则直接调用第一级,如果区块过小,则采用memory pool 整理的方法。

SGI将其简单的包装了一个接口,simple_alloc类。使其符合STL规格。而在使用的时候全部使用的simple_alloc接口(缺省使用)。

 

第一级配置器:

调用malloc()和realloc(),如果配置不成功,则调用oom_malloc(), oom_realloc()(PS:oom= out of memory)。在oom_XXX的处理时,通过不断调用“内存不足处理例程”,期望在某次调用之后能够分配到。设计“内存不足处理例程”是客户端的责任。(effective C++ 对new-handler的行为有一个条款,还没认真看)

第二级配置器:

由于太多小额的区块会造成内存的碎片,同时也会带来额外的负担,因此通过第二级配置器的分配,适当的将小额内存分配出去。当区块小(<128bytes)的时候,采用内存池管理。第二级配置器维护了16个free-lists,各自管理从8~128bytes的小额区块,每一个区块以8bytes递增(将每一个小额的区块需求上调至8的倍数,以便管理)。每一次需要时候就将适当的内存从free-lists中间拔出来。客户释放后就回收到free-lists中。当需要的free-lists中没有可用的区块的时候,调用refill()函数为free-lists填充新的空间(取自内存池,用chunk_alloc()函数)。内存池的处理十分复杂,看的一头雾水。

 

SGI配置器使用方法:

 

template  < class  T,  class  Alloc  =  alloc >

Class vector
{

public:

       typedef T value_type;

       …

protected:

       typedef siple_alloc
<value_type, Alloc> data_allocator;

       …

}
;

 

其中第二个template参数接受缺省的alloc,可以是第一级配置器也可以是第二级配置器。SGI STL已经把它设定为第二级配置器。

 

 

3. 内存基本处理工具

在头文件<stl_uninitialized>中,定义了3个全局函数,uninitialized_copy(),uninitialized_fill(),uninitialized_fill_n(). 看到这些名字应该就知道它们是什么作用了,它们负责在已分配好的空间进行构造。这三个函数都具有”commit or rollback”语意。要么构造出所有元素,要么不构造任何东西。

在具体的实现中,这三个函数也用到了traits技法,通过traits萃取出元素的特性,如果是POD(Plain Old Data)型别,说明其拥有trivial ctor/ dtor/ copy/ assignment函数,因此可以使用copy()、fill()、fill_n()等算法,如果不是POD型别就只能够循环调用construct()函数了。这里用到的也是函数重载的方法,和上面destroy用到的方法是一样的。

注意:在unitialized_copy()实现的时候,针对char*和wchar_t*两种型别,可以采用最有效率的memmove()来复制,因此需要两份特化版本。

你可能感兴趣的:(SGI STL空间配置器(STL源码剖析))