整个STL的操作对象都放在容器之内,而容器一定是需要空间配置器以置放资料。空间配置器需要提供如下接口(下面只列出了主要的接口)
template<class T> class allocator{ public: typedef T value_type; typedef T* pinter; typedef const T* const_pointer; typedef T& reference; typedef const T& const_reference; typedef size_t size_type; typedef ptrdiff_t difference_type; pointer allocator::allocate(size_type n,const void* =0);//配置空间 void allocator deallocate(pointer p,size_type n);//归还配置的空间 void allocator::construct(pointer p,const T& x);//在配置的空间中初始化对象 等价于new((void*)p)T(x) void allocator::destroy(pointer p);//删除对象,等价于p->~T() }
SGI分别定义了一个标准的空间配置器(std::allocator)和次配置力的空间配置器(std::alloc)
SGI标准的空间配置器
SGI定义了一个符合部分标准、名为allocator的配置器,但是SGI从未用过它,主要原因是效率太低。只把C++中的::operator new和::operator delete简单的封装。allocator类主要利用了下面两个函数来进行空间的配置和空间的归还。
template<class T> inline T* allocate(ptrdiff_t size,T*) { set_new_handler(0); T *tmp = (T*)(::operator new((size_t)size*sizeof(T))); if(tmp == 0){ cerr<<"out of memory"<<endl; exit(1); } return tmp; } template<class T> void deallocate(T* buffer){ ::operator delete(buffer); }
上面的allocator只是基层内存配置/释放行为(::operator new和::operator delete)的一层封装,并没有考虑任何效率上的优化。
FOO *pf = new FOO;//new包含两阶段:(1)::operator new配置内存;(2)调用FOO构造函数
delete pf;delete包含两阶段:(1)析构函数;(2)调用::operator delete释放内存。
STL allocator将上面两阶段分开,定义于<memroy>中,主要包含:
(1)#include<stl_alloc.h> //负责内存空间的申请与释放,定义了一、二级配置器彼此合作,配置器名为 alloc;
(2)#include<stl_construct.h>//负责对象的构造与析构,定义了全局函数construct()和destroy()函数;
(3)#include<stl_uninitialized.h>//定义了一些全局函数用来fill(填充)或者copy(复制)大块内存数据。定义 了un_initialized_copy()、un_initialized_fill()、un_initialized_fill_n()。这些函数不属于配置器的范畴,但是与对象初值设置有关。这些函数最差情况调用construct,最佳情况调用memmove()进行内存数据移动。
构造和析构基本工具
正如上面所讲,构造和析构的工具在<stl_construct.h>中完成。这里面有两个函数construct()和distroy()函数。construct的版本主要是这样的:
template<class T1, class T2> inline void construct(T1 *p1,const T2& value) { new (p)T1(value); }
inline void destroy(char*,char*){} inline void destroy(wchar_t *,wchar_t *){} template<class T> inline void destroy(T *p) { p->~T(); } template<class FowardIterator> inline void destroy(FowardIterator first,FowardIterator last) { __destroy(first,last,value_type(first)); }
空间的配置与释放
对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,STL对此的设计哲学如下:向system heap要求空间;考虑多线程(multi-threads)状态;考虑内存不足时的应变措施;考虑过多“小型区块”可能造成的内存碎片(fragment)问题。
C++的内存配置操作是:operator new(),内存的释放基本操作是::operator delete()。这两个全局函数相当于C的malloc()和free()函数。SGI正是以malloc和free完成内存的配置和释放。
考虑到小型区块所可能造成的内存破碎问题。SGI设计了双层级配置器,第一级配置器直接使用malloc()和free(),第二级配置器则视情况采用不同的策略;当配置区块超过128Bytes,视之为足够大,使用第一级配置器。但配置块小于128Byte时,视之为过小,为了降低额外负担,便采用复杂的memory pool整理方式。整个配置器开放哪一级配置器取决于__USE_MALLOC是否被定义:
#ifdef __USE_MALLOC typedef __malloc_alloc_template<0> malloc_alloc; typedef malloc_alloc alloc; //第一级配置器 #else typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS,0> alloc;//第二级配置器 #endif //SGI再包装了一个接口 simple_alloc template<class T,class Alloc> class simple_alloc{ public: static T* allocate(size_t n) {return 0==n?0:(T*)Alloc::allocate(n*sizeof(T))}//这里只是一个转调用 static T* allocate(void) {return (T*)Alloc::allocate(sizeof(T))}//这里只是一个转调用 static T* deallocate(T *p) {if(0!=n)Alloc::deallocate(p,n*sizeof(T))}//这里只是一个转调用 static T* deallocate(T *p) {Alloc::deallocate(p,sizeof(T))}//这里只是一个转调用 } template<class T,class Alloc=alloc>//缺省调用alloc为配置器 class vector{ protected: typedef simple_alloc<value_type,Alloc>data_allocator; void deallocate(){ if(..)data_allocator::deallocate(start,end-start); } }
STL第一级配置器:alloc直接使用malloc(),deallocate直接使用free();模拟C++的set_new_handler以处理内存不足的状况。第一级空间配置器提供了两个对外的接口:allocate和deallocate。(1)allocate采用malloc来分配空间,当分配空间失败的时候,第一级空间配置器模拟了::operator new的set_new_handler功能。set_new_handler是客户来设置的,其功能就是不断尝试释放资源,然后再尝试分配,看能不能成功。如果客户没有设置相应了相应的set_new_handler函数,则allocate会尝试按照set_new_handler提供的功能做,如果没有提供则抛出异常,表示分配不成功。(2)deallocate只是简单的调用free来释放分配好的空间。
STL第二级配置器:维护16个自由链表(free lists)负责16种小型块的次配置能力,内存池(memory pool)以malloc配置而得,如果内存不足,转去调用第一级配置器;如果需求大于128Bytes,转去调用第一配置器。第二级空间配置器也同样提供了两个接口:
(1)allocate:这个函数首先会判断要分配的空间是否大于128字节,如果大于就交个第一级空间配置器完成这个功能;如果小于128字节,allocate首先检查free_list能不能满足分配请求,能满足,直接返回。如果free_list相应链表为空,则调用refill函数;refill函数首先尝试从内存池里面分配一些空间给客户,如果内存池不能完全满足客户的要求,则先分配一部分。如果内存池不能满足客户的请求,调用chunk_alloc从堆内存空间分配一些空间,这些空间分成三部分,一部分给用户,一部分给free_list,,最后部分留给内存池。如果堆内存也没有空间了,就尝试从free_list其他链表里面拿出一些空间。如果其他free_list也没空间了,则交给第一级空间配置器,看不能让new_handler指针所指的函数去释放一些空间,不能则抛出异常。
(2)deallocate:首先会判断要释放的空间是否大于128字节,大于则交给第一级空间配置器去释放,小于则归还到free_list里面。
第二级空间配置器的内存分配实现
如果区块够大,超过128bytes,就移交第一级空间配置器处理,当小于128bytes时,则以内存池管理。每次配置一大块内存,并维护对应之链表(free-list),下次若有相同大小的内存需求,就立即从free-list中拨出。如果客端归还小额区块,负责回收,将它加到相应的free-list链表中。SGI第二级配置将任何小额区块内存需求量上调至8的倍数(例如30,调至32),并维护16个free-list,各管理大小分别为8,16,24,32,40,48,56,64,72,80,88,96,104,112,120,128的小额区块:
union obj{ union obj* free_list_link; char client_data[1]; }具体操作过程是这样的:先检查free-list,如果有可用的块,则直接拿来用,如果没有可用块,就将区块大小上调至8的倍数,然后调用refill(),准备为free-list重新填充空间。chunk_alloc(32,20),于是malloc配置20*2*32Byte区块,第一个区块返回个用户,19个32Bytes块交个free-list维护,20个32Bytes块留给内存池。接下来chunk_alloc(64,20),此时free-list[7]为空,向内存池要求支持。内存池供应32*20/64=10个块,一个交给用户,另外9个块留给free-list维护。
万一山穷水尽,整个system heap空间都不够了,chunk_alloc()四处寻找有无”尚未用区块“之free-list,找出一块就挖一块交出,找不到就交给第一个空间配置器。第一级配置器也是使用malloc来配置内存,但是有out-of-memory处理机制。
内存基本处理工具
STL定义了五个全局函数,作用于未初始化空间上。它们是construct()、destroy()、uninilialized_copy()、uninilialized_fill()、uninilialized_fill_n()。用这些函数包含<memory>,不过实际定于<stl_uninitialized>