在STL运用中,空间配置器总是隐藏在一切组件(各种容器container)的后面,默默付出。所有STL操作的对象都是存放在容器中的,而容器所需要的空间的申请释放等,正是它在幕后的工作。
1. 建立空间配置器的认知框架:
空间配置器提供给使用者统一的接口(空间申请,构造对象,析构对象,释放空间)
我们先通过自己简化的一个不很标准但却能以小见大的的简单空间配置器入手介绍:
#ifndef _MEMORY_H //编译器控制语句,用于防止重复编译
#define _MEMORY_H
//第一个关键函数,申请_N个_Ty类型的空间(模板函数)
template<class _Ty>
_Ty *_My_Allocate(ptrdiff_t _N, _Ty *){
if (_N < 0)
_N = 0;
return ((_Ty*)operator new((size_t)_N * sizeof (_Ty))); }
//第二个关键函数,在指定空间上构建对象(使用定位new)
template<class _T1, class _T2>
void _My_Construct(_T1 *_P, const _T2& _V){
new((void *)_P) _T1(_V); //placement new}
//第三个关键函数,对象的摧毁即析构,通过去调用对象自己的析构函数析构对象,之后再对空间进行回收
template<class _Ty>
void _My_Destroy(_Ty *_P){
_P->~_Ty();}
//当然C++会以类去构建配置器(一切皆对象嘛!)使用它的方法全在内部封装提供
template<class _Ty>
class my_allocator {
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef _Ty* pointer;
typedef const _Ty* const_pointer;
typedef _Ty & reference;
typedef const _Ty & const_reference;
typedef _Ty value_type;//这些type的设计理由先不用关心,其实是为了萃取,后续博客会提到相关内容
pointer address(reference _X) const//此接口用于返回某个对象的地址
{return (&_X); }
const_pointer address(const_reference _X) const
{return (&_X); }
//类中去申请空间的接口函数,相信大家也明白了上面函数的作用之处了
pointer allocate(size_type _N, const void *){
return (_My_Allocate((difference_type)_N, (pointer)0)); }
char* _Charalloc(size_type _N){//特定类型,直接申请相应字节大小空间
return (_My_Allocate((difference_type)_N,(char *)0)); }
void deallocate(void *_P, size_type){//回收空间函数接口
operator delete(_P); }
void construct(pointer _P, const _Ty& _V){//申请空间后去构造对象的接口
_My_Construct(_P, _V); }
void destroy(pointer _P)//析构对象接口
{
_My_Destroy(_P);
}
size_t max_size() const//可最大配置的空间
{
size_t _N = (size_t)(-1) / sizeof (_Ty);
return (0 < _N ? _N : 1);
}
};
2.SGI空间配置器介绍
相必你会说,上面所写的简单空间配置器不就是对malloc delete 定位new和析构的一个简单封装吗?没错上面的代码何谈效率,就是一个简单封装,想让大家明白空间配置器的基本思路,而接下来我没们要去看看SGI空间配置器,你会为它的精彩叹息!
STL标准告诉我们,配置器定义于之中,SGI中,包含两个头文件:stl_alloc.h //负责对内存空间配置和释放
stl_construct.h //负责对象的构造和析构
我们会就内存释放配置作主要介绍stl_alloc.h,主要是向system heap要求空间,并考虑多线程情况,考录内存不足时的处理情况和过多小区块可能造成的内存碎片(fragment)。
它所以设计了双层配置器,第一级空间配置器直接使用malloc()和free()完成,第二级配置器则视情况采用不同的策略,当配置区块大于128bitys时认为足够大,便使用第一级空间配置器,相反则使用第二级空间配置器以降低负担,采用memory pool整理方式。
#ifdef __USE_MALLOC
...
typedef __malloc_alloc_template<0> nalloc_alloc;
typedef malloc_alloc alloc;//令alloc为第一级空间配置器
# else
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc
//令alloc为第二级空间配置器
#endif
//其中__malloc_alloc_template为第一级空间配置器,__default_alloc_template为第二级空间配置器
//但无论alloc被定义为第一级空间配置器还是第二级,SGI还为它包装了一个接口如下,使得配置器接口符合标准(只是简单的转调用,但调用传给的可能是第一或第二级空间配置器,而所有使用它的容器都通过此接口使用):
typedef __malloc_alloc_template<0> malloc_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 void deallocate(T *p, size_t n)
{ if (0 != n) Alloc::deallocate(p, n * sizeof (T)); }
static void 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 deallocator::deallocator(start, end_of_storage - start);
....
};
我们先对STL空间配置器有一个框架认识,并对它的一二级空间配置器运作方式有个了解,下一篇将会对一二级空间配置器作深入代码剖析。