zoukankan      html  css  js  c++  java
  • STL源码分析之空间配置器

    前言

    SGI STL将new的申请空间和调用构造函数的两个功能分开实现, 如果对new不太清楚的, 可以先去看看这一篇new实现再来看配置器也不迟. 本节是STL分析的第一篇, 主要分析STL各个部分都会出现的alloc实现, 虽然每个部分都只会默认调用它, 不了解它也可以看懂分析, 但是他又是不可缺少的, 我们就以它做为开篇进行分析. 

     

    "new"的实现

    这里直接我们直接来看STL的construct实现吧

    1   // 这里的construct调用的是placement new, 在一个已经获得的内存里建立一个对象
    2   template <class T1, class T2>
    3   inline void construct(T1* p, const T2& value) 
    4   {
    5     new (p) T1(value);
    6   }

    可以明白这里就只是一个placement new的调用, 只是用了泛型来实现一个对象分配的模板, 并实现初始化.

    既然已经看到了对象的分配, 那再接再厉看看空间的分配, 充分了解STL是怎么将new分开执行的. allocate函数实现空间的申请, 但是这里有一点看不出来, 申请内存是有分为一级配置器和二级配置器, 分配的空间小于128字节的就调用二级配置器, 大于就直接使用一级配置器, 一级配置器直接调用malloc申请, 二级使用内存池.

     1   template<class T>
     2   inline T* allocate(ptrdiff_t size, T*)
     3   {
     4       set_new_handler(0);
     5       T* tmp = (T*)(::operator new(size)(size * sizeof(T)));
     6       if(!tmp)
     7       {
     8           cerr << "out of memort" << endl;
     9           exit(1);
    10       }
    11       return tmp;
    12   }

    内存分配果然是调用operator new来执行空间分配, 这里allocate和construct都只是简单的对operator new进行封装.

     1   const int N = 4;
     2   int main()
     3   {
     4       allocator<string>  alloc;
     5       auto str_ve = alloc.allocate(N);
     6       auto p = str_ve;    // vector<string> *p = str_ve;
     7       alloc.construct(p++);
     8       alloc.construct(p++, 10, 'a');
     9       alloc.construct(p++, "construct");
    10       cout << str_ve[0] << endl; //  " "空的
    11       cout << str_ve[1] << endl; // aaaaaaaaaa
    12       cout << str_ve[2] << endl; // construct
    13       
    14       while(p != str_ve)
    15       {
    16           alloc.destroy(--p);
    17       }
    18       alloc.deallocate(str_ve, N);
    19 20       exit(0);
    21   }

    这个程序首先调用allocate申请N个大小的空间, 在依次construct调用构造函数, 这里就先初始化3个结构, 紧接着通过destory调用析构函数, 最后deallocate释放申请的空间. 整个过程很容易理解, 但是这里还要深入是dealllocate和destroy两个函数.

     

    "delete"实现

    先是看destroy调用析构函数. 而destroy有两个版本.

    版本一:

    需要传入的参数 : 一个指针

      // 第一版本, 接收指针
      template <class T> inline void destroy(T* pointer) 
      {
          pointer->~T();
      }

    版本一直接就调用了析构函数, 不用过多的分析.

     

    版本二:

    需要传入的参数 : 两个迭代器

      // 第二个版本的, 接受两个迭代器, 并设法找出元素的类型. 通过__type_trais<> 找出最佳措施
      template <class ForwardIterator>
      inline void destroy(ForwardIterator first, ForwardIterator last) 
      {
        __destroy(first, last, value_type(first));
      }
      ​
      // 接受两个迭代器, 以__type_trais<> 判断是否有traival destructor 
      template <class ForwardIterator, class T>
      inline void __destroy(ForwardIterator first, ForwardIterator last, T*) 
      {
        typedef typename __type_traits<T>::has_trivial_destructor trivial_destructor;
        __destroy_aux(first, last, trivial_destructor());
      }

    destroy直接调用 __destroy, 前者只是一个接口, 所以重点是在后者.

    分析 __type_traits<> : 它是用于获取迭代器所指对象的类型,运用traits技法实现的.只要记住我们用来获取对对象类型就可以了. 然后通过类型的不一样选择执行不同的析构调用.

     

    __type_traits__false_type时, 调用的是下面这个函数, 通过迭代所有的对象并调用版本一的函数执行析构函数进行析构. 而这个是被称为non-travial destructor

      
      // 没有non-travial destructor 
      template <class ForwardIterator>
      inline void __destroy_aux(ForwardIterator first, ForwardIterator last, __false_type) 
      {
        for ( ; first < last; ++first)
          destroy(&*first);
      }
    
    

    __type_traits__true_type时, 什么也不做, 因为这样效率很高效, 并不需要执行析构函数. 而这个是被称为travial destructor.

      // 有travial destructor
      template <class ForwardIterator> 
      inline void __destroy_aux(ForwardIterator, ForwardIterator, __true_type) {}

     

    最后是版本二的特化版, 同样也什么都不用做, 没有必要做析构.

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

    destroy分为这么几个版本和几个不同的函数执行都是为了提升效率, 较小的调用并不能看出什么, 但是如果是范围析构的话这样不同的选择析构能很节约时间和效率.

     

    讲解完了destory后应该就能明白上面代码循环执行析构函数了.


    小结

    这里用一个小小的例子来理解"new"和"delete"运算符, 理解new, delete每步分开执行, 内存释放(deallocate)这里没有讲解, 也只是简单的调用free函数. STL这样做1. 为了效率, 2. 为了构建内存池.

    最后将所有的函数进行封装到allocator, 所以例子中都是调用的构造析构等都是封装在该类中.

     

  • 相关阅读:
    点击图片跳转详情
    offsetwidth/clientwidth的区别
    css中让元素隐藏的多种方法
    js中的||、&&与!用法
    怎么区分静态网页和动态网页
    我的第一篇博客--新手勿喷
    2015腾讯暑期实习生面试
    ring0 与 ring3 层之间的交互
    驱动层得到进程的完整路径
    WinDbg调试流程的学习及对TP反调试的探索
  • 原文地址:https://www.cnblogs.com/0xfffffff0/p/10067437.html
Copyright © 2011-2022 走看看