zoukankan      html  css  js  c++  java
  • 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 类内部的成员函数大部分都是调用迭代器进行操作,并没有涉及到空间配置器,这样耦合性自然会降低,更具有扩展性。

      

    既然选择了远方,便只顾风雨兼程
  • 相关阅读:
    STR[#6]
    整数
    一些应该记住的东西(持续更新?再也不会更新了)
    退役后的续命杂谈
    51Nod 快速傅里叶变换题集选刷
    支配树学习笔记
    动态点分治入门随讲
    KD树小结
    HNOI2013 BZOJ3142 数列
    BZOJ2001 HNOI2010 城市建设
  • 原文地址:https://www.cnblogs.com/Forever-Road/p/6806325.html
Copyright © 2011-2022 走看看