STL初探——空间的配置与释放std::alloc

  在STL源代码中,对象构造前的空间配置和对象析构后的空间释放,由<stl_alloc.h>负责,SGI STL的空间配置器考虑到了多线程状态以及线程资源分配和线程切换、内存不足、内存堆区空间的申请、过多“小型区块”可能造成内存碎片(fragment)等一系列问题。

  C++负责内存配置基本操作的是 ::operator new() ,负责内存释放基本操作的是 ::operator delete(),这两个全局函数相当于C的 malloc() 和 free() 函数,因此,SGI 以 malloc() 和 free() 完成了内存的配置与释放。

  考虑到小型区块所可能造成的内存破碎问题,SGI设计了双层级配置器,第一级配置器使用 malloc() 和 free() ,第二级配置器视情况采用不同的策略:配置区块超过128bytes时,视之为“足够大”,调用第一级配置器,当配置区块小于或等于128bytes时,视之为“过于小”,为了降低额外负担,不伤害配置器的效率,便采用复杂的内存池(memory pool)整理空间的方式,而不再求助于第一级配置器。SGI STL 中,配置器名:

  第一级配置器:__malloc_alloc_template

  第二级配置器:__default_alloc_template

  整个设计究竟只开放第一级配置器,或者是同时开放第二级配置器,取决于 __USE_MALLOC是否被定义,咱们可以简单测试一下,如下(VS2015):

  

   显然 SGI STL 并没有定义 __USE_MALLOC,需要注意的是,alloc并不接受任何template型别的参数,可以看看源代码,毫无疑问只能接受一个int型的__inst参数,不能使用模板型别的参数(类似于 template <class T, class U> 中的 T和U,称其为模板型别参数):


template <int __inst> class __malloc_alloc_template { private: static void* _S_oom_malloc(size_t); static void* _S_oom_realloc(void*, size_t); #ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG static void (* __malloc_alloc_oom_handler)(); #endif public: static void* allocate(size_t __n) { void* __result = malloc(__n); if (0 == __result) __result = _S_oom_malloc(__n); return __result; } static void deallocate(void* __p, size_t /* __n */) { free(__p); } static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz) { void* __result = realloc(__p, __new_sz); if (0 == __result) __result = _S_oom_realloc(__p, __new_sz); return __result; } static void (* __set_malloc_handler(void (*__f)()))() { void (* __old)() = __malloc_alloc_oom_handler; __malloc_alloc_oom_handler = __f; return(__old); } };

  当然无论 alloc 被定义成第一级还是第二级配置器, SGI 都将 alloc 进行了上层封装,类似于一个转接器,使其配置器的接口符合 STL 规范,如下:

template<class _Tp, class _Alloc>
class simple_alloc {

public:
    static _Tp* allocate(size_t __n)
      { return 0 == __n ? 0 : (_Tp*) _Alloc::allocate(__n * sizeof (_Tp)); }
static _Tp* allocate(void) { return (_Tp*) _Alloc::allocate(sizeof (_Tp)); }
static void deallocate(_Tp* __p, size_t __n) { if (0 != __n) _Alloc::deallocate(__p, __n * sizeof (_Tp)); }
static void deallocate(_Tp* __p) { _Alloc::deallocate(__p, sizeof (_Tp)); } };

  内部的四个成员函数只是单纯的调用, 调用传递给配置器的成员函数,可能是第一级也有可能是第一级,这个接口不再以bytes为基本单位,而是直接用sizeof(_Tp)作为配置单位,SGI STL 的所有容器全部用该接口,比如我们常用的vector的定义:

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_of_storage - start);
        }
    }
    //...
    
};

  当然我看的是最新的SGI STL源码,有点儿不一样,在定义 vector 容器之前,源码中还发现了一个 _Vector_base 的基类,由其派生出 vector 类,不过在基类中也使用了 simpe_alloc 这个统一封装的接口:

template <class _Tp, class _Alloc> 
class _Vector_base {
public:
  typedef _Alloc allocator_type;
  allocator_type get_allocator() const { return allocator_type(); }

  _Vector_base(const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
_Vector_base(size_t __n,
const _Alloc&) : _M_start(0), _M_finish(0), _M_end_of_storage(0) { _M_start = _M_allocate(__n); _M_finish = _M_start; _M_end_of_storage = _M_start + __n; } ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); } protected: _Tp* _M_start; _Tp* _M_finish; _Tp* _M_end_of_storage;
//////////看这个,还是调用了simple_alloc这个接口, typedef simple_alloc
<_Tp, _Alloc> _M_data_allocator;
//////////
_Tp
* _M_allocate(size_t __n) { return _M_data_allocator::allocate(__n); }
void _M_deallocate(_Tp* __p, size_t __n) { _M_data_allocator::deallocate(__p, __n); } }; #endif /* __STL_USE_STD_ALLOCATORS */

  vector 则保护继承 _Vector_base :

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc> 
{
//........
protected: void _M_insert_aux(iterator __position, const _Tp& __x); void _M_insert_aux(iterator __position); public: iterator begin() { return _M_start; } const_iterator begin() const { return _M_start; } iterator end() { return _M_finish; } const_iterator end() const { return _M_finish; }

//...........

}

  把空间配置与容器操作分隔成两个类,这样一旦空间配置器需要修改或者升级,只需要修改基类就行了,而 vector 类内部的成员函数大部分都是调用迭代器进行操作,并没有涉及到空间配置器,这样耦合性自然会降低,更具有扩展性。

  

既然选择了远方,便只顾风雨兼程
原文地址:https://www.cnblogs.com/Forever-Road/p/6806325.html