zoukankan      html  css  js  c++  java
  • C++ Standard Stl SGI STL源码学习笔记(07) stl_vector 与 一些问题的细化 3 resize函数剖析

      前面在介绍push_back函数的时候有说到placement new的用法.前面说的很简单.这几天处理一些其他的事情,直到昨天下午

    才有时间看源码,顺便安静的看一下书. 其中我又看到了挂关于placement new的介绍,那么就在文章开始之前先说一下这个问题.

      placement new又称"定为new"(我想这纯粹是翻译过来的意思.),当在禁止使用Heap分配的时候,也就是声明对象的构造函数

    为private的时候,我们不能调用构造函数去构造一个对象,这个时候就可以使用placement new. 前一段时间我在阅读sig stl源码的

    时候也看到了stl容器对于placement new的使用.

      placement new 的作用是在一个特定的位置放置一个对象,所以不需要调用构造函数,但是却和构造函数的作用相同. 

    需要注意的是placement new并不分配实际的存储区域, 而是返回一个指向已经分配好空间的指针.所以不要对其执行delete操作.

      但是确实创建了一个对象,如果想要销毁对象,那么需要调用嗯对象的析构函数.

           placement大多都是使用在容器技术中,而且是高级技术,也通常用来隐藏技术实现的细节,这里只做简单了解.

     

    前面很多文章都是介绍stl_vector,这篇文章会介绍vector的resize函数,并作为结尾. 先看一下resize函数的源码:

      void resize(size_type __new_size, const _Tp& __x) {
        if (__new_size < size()) 
          erase(begin() + __new_size, end());    // 擦除begin()+__new_size -> end()之间的元素
        else
          insert(end(), __new_size - size(), __x);
      }
      void resize(size_type __new_size) { resize(__new_size, _Tp()); }  // 这和上面一样,只不过是提供默认的参数.
    

    1. 首先第一点很容易看得出,erase函数执行的是擦除工作,并不能分配内存.

    2. insert在__new_size >= size()的时候会执行内存的重新分配.

     

    先看一下erase的源码:

      iterator erase(iterator __first, iterator __last) {
        iterator __i = copy(__last, _M_finish, __first);
        destroy(__i, _M_finish);
        _M_finish = _M_finish - (__last - __first);
        return __first;
      }
    

     跟着copy的源码走下去,最终会看到最后的实现是:(__copy_trivial)

    template <class _Tp>
    inline _Tp*
    __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result) {
      memmove(__result, __first, sizeof(_Tp) * (__last - __first));
      return __result + (__last - __first);
    }
    

     其实上面的erase在resize函数中使用的时候是不会执行copy函数的.因为end() == _M_finish.所以只需要将

       begin() + __new_size -> _M_finish之间的元素都销毁.destory源码如下:

    inline void _Destroy(_Tp* __pointer) {
      __pointer->~_Tp();  // 这里不是使用delete,而是调用元素对象本身的析构函数.
    }
    

      找了很多层,执行很多跳转最后才找到上面的最终源码,为什么stl要这么麻烦? 因为stl对于容器的内存是使用placement new

      前面说过,所以需要调用对象本身的析构函数来完成工作.

     

    3. 接下来,我们看看,如果__new_size > size()执行insert函数的情况.insert函数源码如下:

      void insert (iterator __pos, size_type __n, const _Tp& __x)
        { _M_fill_insert(__pos, __n, __x); }
    

      下面调用到_M_fill_insert函数,这个函数在前面的文章中有讲解过.不过当时只讲解了该函数的一部分. 本文来看看上半部分. 

    template <class _Tp, class _Alloc>
    void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, 
                                             const _Tp& __x)
    {
      if (__n != 0) {   // __new_size - size() > 0 
        if (size_type(_M_end_of_storage - _M_finish) >= __n) {
          _Tp __x_copy = __x;
          const size_type __elems_after = _M_finish - __position;
          iterator __old_finish = _M_finish;
          if (__elems_after > __n) {
            uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);
            _M_finish += __n;
            copy_backward(__position, __old_finish - __n, __old_finish);
            fill(__position, __position + __n, __x_copy);
          }
          else {
            uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);
            _M_finish += __n - __elems_after;
            uninitialized_copy(__position, __old_finish, _M_finish);
            _M_finish += __elems_after;
            fill(__position, __old_finish, __x_copy);
          }
        }
    

      在上面的注释中,只会调用_M_fill_insert函数的前部分,而后半部分的源码在前面讲解过了.

        情况又作为两种子情况划分:

          1. size_type(_M_end_of_storage - _M_finish) >   __n(__n = __new_size - size() )

          2. size_type(_M_end_of_storage - _M_finish) <= __n(__n = __new_size - size())

      

    对于这两种情况该如何解释呢?如何去理解. _M_end_of_storage代表着存储能力,_M_finish代表着当前的已存储位置, size()

    返回的不是容器的存储能力,而是当前已经存储的元素个数. 理解了这些,上面的两种情况就比较好理解了.  

      

      大的前提条件是:需要resize的新长度已经大于已经存储的长度.

        对于1情况: __new_size在_M_end_of_storage内. 也就是在存储能力内.

        对于2情况: 则不在存储能力范围内了,需要扩大存储能力,也就是扩大存储内存单元.

    好吧,我们去看看STL源码是如何处理的.

     

    对于情况1:

      if (size_type(_M_end_of_storage - _M_finish) >= __n) {
    	      _Tp __x_copy = __x;
    	      const size_type __elems_after = _M_finish - __position;
    	      iterator __old_finish = _M_finish;
    	      if (__elems_after > __n) {  // 这下面的语句不会执行,因为_M_finish = _position 
    	        uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);
    	        _M_finish += __n;
    	        copy_backward(__position, __old_finish - __n, __old_finish);
    	        fill(__position, __position + __n, __x_copy);
    	      }
    	      else {  // 从这里开始执行.
    	        uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);
    	        _M_finish += __n - __elems_after;                    // _M_finish 向后移动__n-1
    	        uninitialized_copy(__position, __old_finish, _M_finish);   
    	        _M_finish += __elems_after;
    	        fill(__position, __old_finish, __x_copy);
    	      }
    	    }
    

      我们假设容器中存储的元素类型不是POD,所以追溯源码就可以找到这里:

    template <class _ForwardIter, class _Size, class _Tp>
    _ForwardIter
    __uninitialized_fill_n_aux(_ForwardIter __first, _Size __n,
                               const _Tp& __x, __false_type)
    {
      _ForwardIter __cur = __first;
      __STL_TRY {
        for ( ; __n > 0; --__n, ++__cur)
          _Construct(&*__cur, __x);
        return __cur;
      }
      __STL_UNWIND(_Destroy(__first, __cur));
    }
    

      很容易看得出,就是从_M_finish到后面的_n个位置都使用placement new初始化元素为__x.

      接着查看uninitialized_copy的源码可以发现并不会执行: 和前面一样,元素类型同样不是POD

    template <class _InputIter, class _ForwardIter>
    _ForwardIter 
    __uninitialized_copy_aux(_InputIter __first, _InputIter __last,
                             _ForwardIter __result,
                             __false_type)
    {
      _ForwardIter __cur = __result;
      __STL_TRY {
        for ( ; __first != __last; ++__first, ++__cur)   // _position == old_position , 所以不会执行.
          _Construct(&*__cur, *__first);
        return __cur;
      }
      __STL_UNWIND(_Destroy(__result, __cur));
    }
    

      

       好吧,对于情况1的分析,已经很清楚了.下面看看情况2.

       const size_type __old_size = size();        
          const size_type __len = __old_size + max(__old_size, __n);  // 这里不是简单的扩张两倍.因为不知道new_size和old_size之间的关系.
          iterator __new_start = _M_allocate(__len);           // 重新申请内存
          iterator __new_finish = __new_start;              // 初始化
          __STL_TRY {
            __new_finish = uninitialized_copy(_M_start, __position, __new_start);  // 将原数组中_M_start -> _M_finish之间的元素复制到新数组中
            __new_finish = uninitialized_fill_n(__new_finish, __n, __x);       // 初始化_new_finish -> _new_finish + __n 之间的元素
            __new_finish
              = uninitialized_copy(__position, _M_finish, __new_finish);           
    

      情况2执行的代码相比较情况1要少的多,尽管要执行新的内存分配. 在上面的源码注释中,我也注明了,这里不是简单的扩张为原来的两倍.

      为什么要这么做呢? 原因其实很简单,因为resize时候,__new_size和size()之间的关系是不知道的,有可能是三倍四倍,也有可能是二倍,

      或者说是介于这些数字之间,所以不应该用一个确切的数字来决定.

      不知道会不会有人问一个问题:  关于内存扩张_len的确定为什么和_M_end_of_storage没有关系了,就是为什么和存储能力没有关系了。

      而是和size()有关系.  额,答案是这个样子的.在前面分析两种情况的时候就说明了,情况2是已经不在存储范围内了,所以需要结合这些基本情况

      联系在一起考虑.

     

    最后对于情况1,情况2都执行相同的源码:  

        destroy(_M_start, _M_finish);
          _M_deallocate(_M_start, _M_end_of_storage - _M_start);
          _M_start = __new_start;
          _M_finish = __new_finish;
          _M_end_of_storage = __new_start + __len;
    

      执行一些初始化和清理工作,收尾.

      

          到这里,关于resize函数的介绍就都结束了. 

     

     

      小结:

        STL中容器对于元素的存储在底层使用的都是数组,而实现数据结构都是使用_M_start,_M_finish,_M_end_of_storage.

        STL中的函数提供的通用性是很好的,而且源码的设计与数据结构的实现很精巧,同时也是很复杂的

     

     

     

     

     

     

     

      

     

     

     

     

      

     

     

     
  • 相关阅读:
    20145328 《信息安全系统设计基础》第6周学习总结
    20145328 《信息安全系统设计基础》第5周学习总结
    2017-2018-2 《网络对抗技术》 20155322 第二周 Exp1 PC平台逆向破解(5)M
    2017-2018-1 《信息安全系统设计基础》 20155322 十六周 课上实践
    2017-2018-1 《信息安全系统设计基础》 20155322 十六周 课下实践
    20155322 2017-2018-1《信息安全系统设计基础》课程总结
    20155322 2017-2018-1《信息安全系统设计基础》第十四周学习总结
    20155322 2017-2018-1 《信息安全系统设计基础》 第十三周学习总结
    20155322 2017-2018-1《信息安全系统设计基础》实验五-通信协议设计
    20155322 2017-2018-1《信息安全系统设计基础》实验四-外设驱动程序设计
  • 原文地址:https://www.cnblogs.com/respawn/p/2621125.html
Copyright © 2011-2022 走看看