随笔-16  评论-7  文章-0  trackbacks-0

空间配置器的标准接口(根据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()来复制,因此需要两份特化版本。

posted on 2010-05-28 23:04 dead_horse 阅读(1046) 评论(0)  编辑 收藏 引用 所属分类: STL读书笔记

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理