C++ Standard Stl SGI STL源码学习笔记(06) stl_vector 与 一些问题的细化 2 push_back函数剖析

  上一篇文章中,关于stl_vector的故事只是个开始.  这篇文章中,接着去分析vector中的细节问题. 

 

  再次声明,我没有看过关于stl源码分析方面的书籍,强调这一点是为了不会让别人误会我是从别的地方抄袭的. 另外,欢迎大家拍砖。

  后面陆续的几篇文章都会详细分析vector中的函数实现. 我尽量做到篇幅不大,而且能够尽量用自己的语言和自己分析的结果给大

  家展现实现的过程. 这样不会浪费大家的时间.写的不好就请原谅了.

 

    push_back函数的作用就是从_M_finish指向的位置开始插入数据。如果预先分配的存储单元不够,则会做扩展处理.会在后面分析的时候

    说明这个问题.

  void push_back(const _Tp& __x) {
    if (_M_finish != _M_end_of_storage) {
      construct(_M_finish, __x);
      ++_M_finish;
    }
    else
      _M_insert_aux(end(), __x);
  }
// **
// *  实现和上面相同,只是在需要初始化的地方都是用默认初始化值
// **
  void push_back() {
    if (_M_finish != _M_end_of_storage) {
      construct(_M_finish);
      ++_M_finish;
    }
    else
      _M_insert_aux(end());
  }

       1. _M_finish和_M_end_of_storage有什么区别?_M_finish和_M_end_of_storage同样都是vector从vector_base中继承来的,

    但是二者的作用是不同的.

      [ _M_finish ] :  数据类型的指针,执行当前操作的数据. 在默认的初始化时候,_M_finish==_M_start.也就是指向底层保存元素

                 数组的头元素.例如不断的往里面push_back数据,那么_M_finish就会不断的像数组的尾端移动,

               直到_M_end_of_storage.

      

 [_M_end_of_storage ] :  _M_end_of_storage在使用vector第一次初始化分配数组内存的时候,是指向数组的最后一个元素的后一个位置

               因为初始化的时候 _M_finish = _M_start, _M_end_of_storage = _M_start + _n. (_n是元素个数).

                 随着不断的对vector进行操作,后面可能会导致_M_end_of_storage发生变化.

 

    说的直白一点就是,_M_finish是_M_start(头指针)和_M_end_of_storage(尾指针的后一个位置)之间游动的游标.

 

  2. 执行push_back(n)的操作的时候,首先会检查数组的容量有没有达到上限,如果_M_finish == _M_end_of_storage.那么就需要扩大

    存储单元,就会在_M_insert_aux(end(),_x)中进行容量的扩增.

 

  3. 如果没有达到上限的话,也就是说有空余的空间,那么就给当前游标_M_finish指向的位置赋值,然后将游标向下一个位置移动.

   

  4. 如果没有达到上限,就使用construct对_M_finish指向的内存区域进行赋值. 

template <class _T1, class _T2>
inline void _Construct(_T1* __p, const _T2& __value) {
  new ((void*) __p) _T1(__value);
}

  对于new的这种用法,不是很常见,但是在<<c++ primer>>中后面"特殊工具与技术"章节有讲解.

new (place_address) type
new (place_address) type(parm-list)

  5. 如果达到上限,就需要扩张存储区域,按照原区域的2倍进行扩张.    

 const size_type __old_size = size();                         // 获取原存储区域长度
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;         // 扩张为原来的2倍
    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之间的数据复制到新的存储区域
      construct(__new_finish, __x); // insert new value.                // 插入新数据,和前面的 操作一样 
      ++__new_finish; // go next.                            // 移动_M_finish
      __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);     
    }
    __STL_UNWIND((destroy(__new_start,__new_finish), 
                  _M_deallocate(__new_start,__len)));
    destroy(begin(), end());
    _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;

  这里可能有些人不明白为什么要调用两次uninitialized_copy函数,其实有点想多了,这要看上面的调用:

    _M_insert_aux(end(), _x). 而end()的作用就是获取_M_finish.而在uninitialized_copy函数中,首先会进行判断.   

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)
      _Construct(&*__cur, *__first);
    return __cur;
  }
  __STL_UNWIND(_Destroy(__result, __cur));
}

   虽然通过很多层的转换,但是还是可以看到判断_first != _last. 所以在上面的调用中这句是不会有执行的.而这看起来有点多此一举是吧?

  不是. 因为_M_insert_aux提供的是通用的使用方法,需要使用一种通用的实现.而上面的调用最终还是会得到 _new_finish = _new_finish.

  所以不需要纠结.

  

  最后,关于push_back的另一个一个参数的版本就不需要多解释了,因为二者之间在实现上方法和原理都是一样的,多说无益.

    

    

 

 

 

 
原文地址:https://www.cnblogs.com/respawn/p/2615446.html