STL源码剖析 --vector

来源:互联网 发布:淘宝源码 编辑:程序博客网 时间:2024/05/16 17:22
vector容器概述
      vector的数据安排以及操作方式,与array非常相似。两者的唯一区别在于空间的运用的灵活性。array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。vector是动态空间,随着元素的加入,它的内部机制会自行扩充空间以容纳新元素。因此,vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必因为害怕空间不足而一开始要求一个大块头的array了,我们可以安心使用array,吃多少用多少。
      vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率。一旦vector的旧有空间满载,如果客户端每新增一个元素,vector的内部只是扩充一个元素的空间,实为不智。因为所谓扩充空间(不论多大),一如稍早所说,是”配置新空间/数据移动/释还旧空间“的大工程,时间成本很高,应该加入某种未雨绸缪的考虑。稍后我们便可看到SGI vector的空间配置策略了。
      另外,由于vector维护的是一个连续线性空间,所以vector支持随机存取

      注意:vector动态增加大小时,并不是在原空间之后持续新空间(因为无法保证原空间之后尚有可供配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间。因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。这是程序员易犯的一个错误,务需小心。

Vector的数据结构

我们从最简单的开始,Vector的数据结构相当简单,由于需要判断内存是否够用,所以要用到三个指针,分别指向头,目前使用空间的尾,目前可用空间的尾。其源代码如下:

//对源码略作调整  iterator _M_start;                  //表示目前使用空间的头  iterator _M_finish;                 //表示目前使用空间的尾(尾:最后一个元素的下一个位置)  iterator _M_end_of_storage;         //表示目前可用空间的尾    iterator begin() { return _M_start; }  iterator end()   { return _M_finish; }    size_type size() const                  //目前空间元素的个数  {      return size_type(end() - begin());  }  size_type max_size() const  {      return size_type(-1) / sizeof(_Tp);  }  size_type capacity() const              //容量  {      return size_type(_M_end_of_storage - begin());  }  bool empty() const  {      return begin() == end();  }  

Vector的迭代器

vector维护的是一段连续的内存空间,所以不论容器中元素的型别为何,普通指针都可以作为vector的迭代器而满足所有必要的条件。vector支持随机存取,所以vector提供的是Random Access Iterator。

下面来看看vector关于迭代器的源码:

template <class T, class Alloc = alloc>class vector{public:// vector内部是连续内存空间,所以迭代器采用原生指针即可  typedef value_type* iterator;    //以下为满足Traits功能定义的内嵌型别  typedef T value_type;  typedef value_type* pointer;                    typedef const value_type* const_pointer;  typedef const value_type* const_iterator;  typedef value_type& reference;                  typedef const value_type& const_reference;  typedef ptrdiff_t difference_type;    typedef size_t size_type;         }

vector的构造函数

默认构造函数


在使用vector的时候,我们通常会有如下定义:

#include <vector>
vector<int> vec;
在上述定义中,调用了vector的默认构造函数,其默认不分配内存空间,如下:

// vector的默认构造函数默认不分配内存空间
vector() : start(0), finish(0), end_of_storage(0) {}

带参构造函数


通常,vector的初始化可以指定元素个数和初始化类型。如下:

vector<int> vec(10,1); // 将vec初始化为10个1

vector提供下面的构造函数以支持上述初始化操作:

带参构造函数

// 构造函数,允许指定vector的元素个数和初值vector(size_type n, const T& value) { fill_initialize(n, value); }vector(int n, const T& value) { fill_initialize(n, value); }vector(long n, const T& value) { fill_initialize(n, value); }// 需要对象提供默认构造函数explicit vector(size_type n) { fill_initialize(n, T()); }/** * 填充并予以初始化 */void fill_initialize(size_type n, const T& value){  start = allocate_and_fill(n, value);  finish = start + n;                         // 设置当前使用内存空间的结束点  //这里不过多的分配内存  end_of_storage = finish;}/** * 配置一块大小为n的内存空间,并予以填充 */iterator allocate_and_fill(size_type n, const T& x){// 调用STL的空间配置器配置一块大小为n的内存空间  iterator result = data_allocator::allocate(n);   // 调用底层函数uninitialized_fill_n予以填充  uninitialized_fill_n(result, n, x);  return result;}
这里面调用了uninitialized_fill_n函数,这个函数是STL的内存基本处理函数,存放在stl_uninitialized.h中,下面来看看它的源码:

// 如果copy construction和operator =等效, 并且destructor is trivial// 那么就可以使用本函数template <class ForwardIterator, class Size, class T>inline ForwardIterator__uninitialized_fill_n_aux(ForwardIterator first, Size n,                           const T& x, __true_type){  return fill_n(first, n, x);}// 不是POD类型使用以下函数template <class ForwardIterator, class Size, class T>ForwardIterator__uninitialized_fill_n_aux(ForwardIterator first, Size n,                           const T& x, __false_type){  ForwardIterator cur = first;  for ( ; n > 0; --n, ++cur)    construct(&*cur, x);  return cur;}// 利用type_traits来判断是否是POD类型template <class ForwardIterator, class Size, class T, class T1>inline ForwardIterator __uninitialized_fill_n(ForwardIterator first, Size n,    const T& x, T1*){  typedef typename __type_traits<T1>::is_POD_type is_POD;  return __uninitialized_fill_n_aux(first, n, x, is_POD());}// 利用Iterator_traits来萃取出其值类型template <class ForwardIterator, class Size, class T>inline ForwardIterator uninitialized_fill_n(ForwardIterator first, Size n,    const T& x){  return __uninitialized_fill_n(first, n, x, value_type(first));}
这里先学习 insert() 函数(后面介绍另一个版本的 insert():插入n个指定元素)

iterator insert(iterator __position, const _Tp& __x)  {      size_type __n = __position - begin();  //插入位置与头的距离      if (_M_finish != _M_end_of_storage && __position == end())  //还有剩余空间,且插入位置恰为尾端      {          construct(_M_finish, __x);  //直接构造新元素          ++_M_finish;                //调整迭代器      }      else          _M_insert_aux(__position, __x);  //没有剩余空间或插入位置为尾前面,则则调用_M_insert_aux()      return begin() + __n;   //返回插入位置  }  

_M_insert_aux()函数:

template <class _Tp, class _Alloc>  void  vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)  {      if (_M_finish != _M_end_of_storage) {          //还有备用空间,表明插入位置不是尾端          construct(_M_finish, *(_M_finish - 1));    //以空间最后一个对象为蓝本构造新对象元素(位于备用空间的起始位置)          ++_M_finish;                               //调整迭代器尾端位置,指向最后一个元素的后一个位置          _Tp __x_copy = __x;                        //赋值对象             copy_backward(__position, _M_finish - 2, _M_finish - 1);          *__position = __x_copy;                    //迭代器位置赋指定值      }      else {     //无备用空间          const size_type __old_size = size();        //保存当前空间中的元素个数          const size_type __len = __old_size != 0 ? 2 * __old_size : 1; //长度为原空间元素个数(不为0时)的两倍          iterator __new_start = _M_allocate(__len);     //分配该长度的空间          iterator __new_finish = __new_start;           //新空间为空(初始化)            __STL_TRY{              __new_finish = uninitialized_copy(_M_start, __position, __new_start); //复制初始化,将插入位置前的元素复制到新空间              construct(__new_finish, __x);    //在指定的插入位置构造新对象,并为新元素设定初值__X              ++__new_finish;                  //调整迭代器位置              //将插入位置后的元素复制到新空间插入元素位置的后面,完成最后的插入操作              __new_finish = uninitialized_copy(__position, _M_finish, __new_finish);            } //捕捉异常,如有一个复制出错,则析构并释放空间          __STL_UNWIND((destroy(__new_start, __new_finish), _M_deallocate(__new_start, __len)));            //析构并释放原 vector          destroy(begin(), end());          _M_deallocate(_M_start, _M_end_of_storage - _M_start);            //调整迭代器,指向新 vector          _M_start = __new_start;          _M_finish = __new_finish;          _M_end_of_storage = __new_start + __len;      }  }  

 push_back() 函数:

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);           //如果没有备用空间,则调用_M_insert_aux()  } 
push_back() 与 insert() 都有调用_M_insert_aux() 的情况,二者不同的是,push_back() 只从尾端插入元素,相当于push_back() 是 insert() 函数的一个特例。或许 push_back() 应该定义为:

void push_back(const _Tp& __x)  {      insert(vector<_Tp>::end(), const _Tp& __x);  }  
从上面可知,vector 增加(插入)新元素时,如果未超过当时的容量,则还有剩余空间,那么直接添加到最后(插入指定位置),然后调整迭代器,这里的 construct() 前面有介绍,是在已分配好的内存上构造对象,这里已分配好的内存定位到剩余空间的起始位置。如果没有剩余空间了,则会重新配置原有元素个数的两倍空间,然后将原空间元素通过复制的方式初始化新空间,再向新空间增加元素,最后析构并释放原空间。

下面学习 pop_back() ,erase(),clear() 等函数的内部实现:

void pop_back() {      --_M_finish;          //调整迭代器位置      destroy(_M_finish);   //析构空间最后一个元素  }    iterator erase(iterator __position) {      if (__position + 1 != end())           //判断擦除位置的有效性          copy(__position + 1, _M_finish, __position);  //将该位置后面的元素复制到前一个位置      --_M_finish;                           destroy(_M_finish);           //析构最后一个元素      return __position;            //返回该位置(指向的元素为原位置的下一个元素)  }    iterator erase(iterator __first, iterator __last) { //擦除一段元素      iterator __i = copy(__last, _M_finish, __first);  //复制last后面的元素到待擦除段      destroy(__i, _M_finish);                          //擦除后面,清楚 __i 的位置      _M_finish = _M_finish - (__last - __first);     //调整迭代器位置      return __first;  }    void clear() { erase(begin(), end()); }  
下面给出局部区间的 erase() 操作图(《STL 源码剖析》)




这里继续学习 insert() 的另一个版本,往指定位置插入 n 个指定元素:insert(position, n, x)

void insert(iterator __pos, size_type __n, const _Tp& __x)  {      _M_fill_insert(__pos, __n, __x);  }    template <class _Tp, class _Alloc>  void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, const _Tp& __x)  {      if (__n != 0)  //插入元素个数不为 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) {              //插入位置后面的元素个数 N 大于插入元素的个数 n,N>n                  uninitialized_copy(_M_finish - __n, _M_finish, _M_finish);  //将尾端前面的n个元素复制到尾端后面                  _M_finish += __n;                            //调整尾端位置                                    //逆向复制,将插入位置position后面的 N-n 个元素复制到原尾端位置前面                  //这样,插入位置后面的均复制到了最后,留下 n 个空间供插入                  copy_backward(__position, __old_finish - __n, __old_finish);                   fill(__position, __position + __n, __x_copy);     //往插入位置后填进 n 个指定元素              }              else {   //插入位置后面的元素个数 N 不大于 插入元素个数,N<=n                  uninitialized_fill_n(_M_finish, __n - __elems_after, __x_copy);//尾端位置后面填进 n-N 个元素                  _M_finish += __n - __elems_after;                //调整尾端位置到原尾端位置后面第 n-N 个位置                  uninitialized_copy(__position, __old_finish, _M_finish);  //将插入位置后的元素复制到调整后的尾端位置后                  _M_finish += __elems_after;                      //调整尾端位置                  fill(__position, __old_finish, __x_copy);      //插入位置到原尾端位置N个元素赋值,前面已赋值n-N个              }          }          else {   //备用空间不足情况,需要重新分配空间              const size_type __old_size = size();   //原空间元素个数              //原空间个数 + 原空间个数与插入个数的较大值,保证可容纳新插入元素              const size_type __len = __old_size + max(__old_size, __n);              iterator __new_start = _M_allocate(__len);               //分配空间              iterator __new_finish = __new_start;                      __STL_TRY{                         __new_finish = uninitialized_copy(_M_start, __position, __new_start);//原空间插入位置前的元素拷贝到新空间                  __new_finish = uninitialized_fill_n(__new_finish, __n, __x);  //填补 n 个元素                  __new_finish = uninitialized_copy(__position, _M_finish, __new_finish); //复制原空间插入位置后的元素              }  //如果中间有失败操作,则析构元素并释放新分配的空间              __STL_UNWIND((destroy(__new_start, __new_finish), _M_deallocate(__new_start, __len)));                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;          }      }  }
下面给出 insert() 各种情况下的操作图


最后再学习 resize(),reserve() 和 swap()函数的内部实现

//将vector大小重置为 __new_size  void resize(size_type __new_size, const _Tp& __x)  {      if (__new_size < size())           //如果新空间大小小于原空间          erase(begin() + __new_size, end());  //则只保留前面 __new_size 个元素      else          insert(end(), __new_size - size(), __x); //如果新空间大一些,则将大于的部分置为指定值  }    //预留至少共容纳 __n 个元素的空间  vector<_Tp, _Alloc>& operator=(const vector<_Tp, _Alloc>& __x);  void reserve(size_type __n) {      if (capacity() < __n) {           //当前容量小于需要预留的空间时,才进行以下操作        const size_type __old_size = size();    //记录元素个数        //分配__n大小的空间,然后将原空间元素复制初始化        iterator __tmp = _M_allocate_and_copy(__n, _M_start, _M_finish);        destroy(_M_start, _M_finish);     //析构原空间元素        _M_deallocate(_M_start, _M_end_of_storage - _M_start);  //释放整个原空间        _M_start = __tmp;                        //调整迭代器        _M_finish = __tmp + __old_size;        _M_end_of_storage = _M_start + __n;      }  }    //交换两个 vector 中的元素  //其实就是交换彼此的迭代器指向位置(迭代器的巧妙功能)  void swap(vector<_Tp, _Alloc>& __x) {      __STD::swap(_M_start, __x._M_start);      __STD::swap(_M_finish, __x._M_finish);      __STD::swap(_M_end_of_storage, __x._M_end_of_storage);  } 


0 0
原创粉丝点击