STL空间配置器-第一级配置器

一、SGI STL配置器简介

 

SGI STL的配置器与众不同,它与标准规范不同。如果要在程序中明确使用SGI配置器,那么应该这样写:

vector<int,std::alloc> iv;  

 

他的名字是alloc,而且不接受任何参数。标准配置器的名字是allocator,而且可以接受参数。

SGI STL的每一个容器都已经指定了缺省配置器:alloc。我们很少需要自己去指定空间配置器。比如vector容器的声明:

template <class T, class Alloc = alloc>  // 预设使用alloc为配置器  

class vector {  

//...  

}  

 

二、SGI标准的空间配置器

 

 

其实SGI也定义了一个符合部分标准,名为allocator的配置器,但是它自己不使用,也不建议我们使用,主要原因是效率不佳。它只是把C++的操作符::operator new::operator delete做了一层简单的封装而已。下面仅仅贴出代码:

#ifndef DEFALLOC_H

#define DEFALLOC_H

 

#include<new.h>

#include<stddef.h>

#include<stdlib.h>

#include<limits.h>

#include<iostream.h>

#include<algobase.h>

 

 

template<class T>

inline T*allocate(ptrdiff_t size, T*) {

    set_new_handler(0);

    T* tmp= (T*)(::operatornew((size_t)(size * sizeof(T))));

    if (tmp==0) {

    cerr<<"outof memory" << endl;

    exit(1);

    }

    return tmp;

}

 

 

template<class T>

inlinevoid deallocate(T*buffer) {

    ::operatordelete(buffer);

}

 

template<class T>

class allocator {

public:

    typedef T value_type;

    typedef T*pointer;

    typedefconst T*const_pointer;

    typedef T&reference;

    typedefconst T&const_reference;

    typedef size_t size_type;

    typedef ptrdiff_t difference_type;

 

    pointerallocate(size_type n) {

    return::allocate((difference_type)n,(pointer)0);

    }

    void deallocate(pointer p) {::deallocate(p); }

    pointeraddress(reference x) {return(pointer)&x; }

    const_pointerconst_address(const_reference x) {

    return (const_pointer)&x;

    }

    size_typeinit_page_size() {

    return max(size_type(1), size_type(4096/sizeof(T)));

    }

    size_typemax_size()const {

    return max(size_type(1), size_type(UINT_MAX/sizeof(T)));

    }

};

 

class allocator<void> {

public:

    typedefvoid* pointer;

};

 

#endif

 

 

三、SGI特殊的空间配置器alloc

 

 

通常,C++中用new操作符来分配内存都包括两个阶段:

1)调用::operator new配置内存

2)调用构造函数来构造对象内容

 

同理,delete操作也包括两个阶段:

1)调用析构函数将对象析构

2)调用::operator delete释放内存

 

为了精密分工,SGI allocator将两个阶段分开

内存配置操作由alloc:allocate负责,内存释放由alloc:deallocate负责;对象构造操作由::contructor()负责,对象析构由::destroy()负责。

 

配置器定义在头文件<memory>中,它里面又包括两个文件:

#include <stl_alloc.h>        // 负责内存空间的配置和器释放  

#include <stl_construct.h>        // 负责对象的构造和析构  

 

下图显示了其结构:

STL空间配置器-第一级配置器_第1张图片

图一头文件<memory>结构

 

 

1、对象的建构和结构函数construct()和destroy()

 

图二显示了这两个函数的结构和功能。他们被包含在头文件stl_construct.h中。

STL空间配置器-第一级配置器_第2张图片

图二函数construct()destroy()示意图

函数construct()使用了定位new操作符,其源代码:

template <class T1, class T2>  

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

  new (p) T1(value);    // 定为new操作符placement new; 在指针p所指处构造对象  

}  

 

函数destroy则有两个版本。

第一个版本较简单,接受一个指针作为参数,直接调用对象的析构函数即可,其源代码:

template <class T>  

inline void destroy(T* pointer) {  

    pointer->~T();   // 调用析构函数  

}  

 

第二个版本,其参数接受两个迭代器,将两个迭代器所指范围内的所有对象析构掉。而且,它采用了一种特别的技术:依据元素的型别,判断其是否有trivialdestructor(无用的析构函数)进行不同的处理。这也是为了效率考虑。因为如果每个对象的析构函数都是trivial的,那么调用这些毫无作用的析构函数会对效率造成影响。

下面看其源代码:

//如果元素的数值型別(value type)有non-trivial destructor

template<class ForwardIterator>

inlinevoid

__destroy_aux(ForwardIteratorfirst, ForwardIterator last, __false_type) {

  for ( ; first< last; ++first)

    destroy(&*first);//调用析构函数

}

 

//如果元素的数值型別(value type)有 trivialdestructor

template<class ForwardIterator>

inlinevoid__destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}//不调用析构函数

 

//判断元素的数值型別(value type)是否有trivial destructor,分别调用上面的函数进行不同的处理

template<class ForwardIterator,class T>

inlinevoid __destroy(ForwardIterator first, ForwardIteratorlast, T*) {

  typedeftypename __type_traits<T>::has_trivial_destructor trivial_destructor;

  __destroy_aux(first,last, trivial_destructor());

}

 

//以下是 destroy()第二版本,接受两个迭代器。它会设法找出元素的数值型別,

//进而利用 __type_traits<>求取最适当措施。

template<class ForwardIterator>

inlinevoid destroy(ForwardIterator first, ForwardIteratorlast) {

  __destroy(first,last, value_type(first));

}

 

第二版本还针对迭代器为char*wchar_t*定义了特化版本:

inline void destroy(char*, char*) {}  

inline void destroy(wchar_t*, wchar_t*) {}  

 

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

 

对象构造前的空间分配和析构后的空间释放,定义在头文件<stl_alloc.h>中。其设计思想是:

  • system heap要求空间。
  • 考虑多线程状态。
  • 考虑内存不足时的应变措施。
  • 考虑过多“小额区块”可能造成的内存碎片问题。

 

考虑到小型区块可能造成的内存破碎问题,SGI设计了双层级配置器,第一级配置器直接使用malloc()free(),第二级则视情况采用不同的策略。而且采用了复杂的内存池memory pool整理方式。整个设计究竟是只开放第一级配置器还是同时开放第二级配置器取决于宏__USE_MALLOC是否被定义。

# ifdef __USE_MALLOC   

...   

typedef __malloc_alloc_template<0> malloc_alloc;//令 alloc为第一级配置器

typedef malloc_alloc alloc;   

# else   

...   

//令 alloc 为第二级配置器   

typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc;   

#endif /* ! __USE_MALLOC */   

SGI并未定义它。

 

无论alloc被定义为第一级或者是第二级配置器,SGI还为它包装一个接口如下,使配置器的接口能够符合STL规格:

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)); }  

};  

 

SGI STL容器全部是使用这个simple_alloc接口。

 

第一级和第二级配置器之间的关系如图三所示。

STL空间配置器-第一级配置器_第3张图片

图三第一级配置器和第二级配置器

 

 

第一级和第二级配置器的包装接口和运用方式如下:

STL空间配置器-第一级配置器_第4张图片

 

 

第一级配置器__malloc_alloc_template剖析

第一级配置器直接使用malloc(),free(),realloc()C函数执行实际的内存配置、释放、重配置操作,并实现出类似C++ newhandler机制。它有独特的out-of-memory内存处理机制:在抛出std::bad_alloc异常之前,调用内存不足处理例程尝试释放空间,如果用户没有定义相应的内存不足处理例程,那么还是会抛出异常。详细实现见函数oom_malloc(),oom_realloc()

 

内存不足处理例程保存在函数指针__malloc_alloc_oom_handler里面。

 

下面列出代码:

#if0

#   include<new>

#   define  __THROW_BAD_ALLOCthrow bad_alloc

#elif!defined(__THROW_BAD_ALLOC)

#   include<iostream.h>

#   define  __THROW_BAD_ALLOC cerr <<"out of memory"<< endl; exit(1)

#endif

 

// malloc-based allocator.通常比稍后介绍的 default alloc速度慢,

//一般而言是 thread-safe,并且对于空间的运用比较高效(efficient)。

//以下是第一级配置器。

//注意,无「template型别参数」。至于「非型别参数」inst,完全没派上用场。

template<intinst>  

class__malloc_alloc_template {

 

private:

//以下都是函式指标,所代表的函式将用来处理内存不足的情况。

//oom : out of memory.

staticvoid *oom_malloc(size_t);

staticvoid *oom_realloc(void*, size_t);

staticvoid (* __malloc_alloc_oom_handler)();//内存不足处理例程,被上面的两个函数调用

 

public:

 

staticvoid * allocate(size_t n)

{

    void  *result=malloc(n);//第一级配置器直接使用 malloc()

    //以下,无法满足需求时,改用 oom_malloc()

    if (0== result) result= oom_malloc(n);

 

    return  result;

}

staticvoid deallocate(void*p, size_t /* n */)

{

free(p); //第一级配置器直接使用free()

}

 

staticvoid * reallocate(void*p, size_t /* old_sz */,size_t new_sz)

{

    void  *  result  =realloc(p, new_sz);//第一级配置器直接使用 rea

    //以下,无法满足需求时,改用 oom_realloc()

    if (0== result) result= oom_realloc(p, new_sz);

 

    return  result;

}

 

//以下模拟 C++ set_new_handler(). 换句话说,你可以透过它,

//指定你自己的 out-of-memory handler

staticvoid (* set_malloc_handler(void (*f)()))()

{

    void  (*  old)()  =  __malloc_alloc_oom_handler;

   __malloc_alloc_oom_handler =f;

    return(old);

}

};//CLASS定义结束

 

//malloc_alloc out-of-memory handling

//初值为 0。有待客端设定。

template<int inst>

void (*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)()= 0;

 

template<int inst>

void*__malloc_alloc_template<inst>::oom_malloc(size_t n)

{

    void  (* my_malloc_handler)();

    void  *result;

 

    //循环

    for (;;)  {

//不断尝试释放、配置、再释放、再配置…

my_malloc_handler =__malloc_alloc_oom_handler;

        if  (0  ==  my_malloc_handler)  

{  __THROW_BAD_ALLOC; }

 

        (*my_malloc_handler)();//呼叫处理例程,企图释放内存。

        result=malloc(n);  //再次尝试配置内存。

 

        if  (result) 

    return(result);

    }

}

 

template<int inst>

void*__malloc_alloc_template<inst>::oom_realloc(void*p, size_t n)

{

    void  (* my_malloc_handler)();

    void  *result;

 

       for(;;)  {  //不断尝试释放、配置、再释放、再配置…

my_malloc_handler =__malloc_alloc_oom_handler;

 

               if  (0  ==  my_malloc_handler)  

{  __THROW_BAD_ALLOC; }

 

                (*my_malloc_handler)();//呼叫处理例程,企图释放内存。

                result= realloc(p, n);//再次尝试配置内存。

 

                if  (result)  

        return(result);

    }

}

 

//注意,以下直接将参数 inst指定为 0

typedef__malloc_alloc_template<0> malloc_alloc;

 

 

需要注意的是:设计“内存不足处理例程”是客端的责任。关于设计“内存不足处理例程”的具体做法可参考《Effective C++(第二版)》条款7

 



你可能感兴趣的:(STL空间配置器-第一级配置器)