STL源码剖析——vector

前言

最近开始看《STL源码剖析》,由于是第一次看,所以开个文章记录和总结下该书印象笔记深刻和重要的知识点。本文主要介绍第三章序列式容器中的vector。

序列式容器

序列式容器为程序员提供了控制元素存储和顺序访问的能力,这种顺序不依赖于元素的值,而是与元素加入容器上的位置想对应。C++语言本身就提供了一个序列式容器array,STL提供vector、list、deque、stack、queue等序列式容器。


序列式容器衍生关系

序列式容器的衍生关系如上图所示,衍生是内含关系(即底层实现)。可以看到heap(堆)是由vector衍生而来,而priority-queue(优先队列)则由heap衍生而来。stack和queue都有deque(双端队列)衍生而来。

vector概述

vector的数据安排以及操作方式与array十分相似,两者唯一差别在于空间运用的灵活性。array是静态空间,一旦配置后则不能改变。而vector是动态空间,随着元素的加入,它内部机制会自行扩充空间以容纳新元素。

vector迭代器

vector维护的是一个连续的线性空间,所以不论其元素为何,普通指针都可以作为vector的迭代器而满足所有必要条件。vector迭代器需要的操作有*,->,++,--,+,-,+=,-+,而普通指针天生就具备这样的能力,所以vector提供的是随机访问迭代器(Random Access Iterators)。

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc> 
{
     typedef _Tp value_type;
     typedef value_type* iterator;   // 可以看出,vector的迭代器是由普通指针实现
}

vector的数据结构与内存管理

vector的数据结构非常简单:线性连续空间,用三个迭代器分别表示其空间可使用和已使用范围,start和finish指向连续空间中已使用的头和尾,end_of_storage则指向整块连续空间(包括已使用和未使用)的尾端。

template <class _Tp, class _Alloc = __STL_DEFAULT_ALLOCATOR(_Tp) >
class vector : protected _Vector_base<_Tp, _Alloc> 
{
    protected:
    #ifdef __STL_HAS_NAMESPACES
    ....
    using _Base::_M_start;   // 表示目前使用的空间头
    using _Base::_M_finish;  // 表示目前使用的空间尾
    using _Base::_M_end_of_storage;   // 表示目前可用的空间的尾
    #endif /* __STL_HAS_NAMESPACES */
}

利用这三个迭代器,便可轻易实现首位标示、大小、容量、判空、[]运算子等功能。

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; }

  reverse_iterator rbegin()
    { return reverse_iterator(end()); }
  const_reverse_iterator rbegin() const
    { return const_reverse_iterator(end()); }
  reverse_iterator rend()
    { return reverse_iterator(begin()); }
  const_reverse_iterator rend() const
    { return const_reverse_iterator(begin()); }

  size_type size() const
    { return size_type(end() - begin()); }
  size_type capacity() const
    { return size_type(_M_end_of_storage - begin()); }
  bool empty() const
    { return begin() == end(); }

  reference operator[](size_type __n) { return *(begin() + __n); }
  const_reference operator[](size_type __n) const { return *(begin() + __n); }
vector示意图

vector构造、内存管理及其元素操作

vector默认使用alloc作为空间配置器,并据此另外定义一个data_allocator,以便以元素大小为配置单位。

typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;

构造时空间分配

vector提供许多构造函数(constructors),默认构造函数会为vector配置0个空间,其中还有一个构造函数允许我们指定配置的空间大小及其初值。

 vector(size_type __n, 
        const _Tp& __value,
        const allocator_type& __a = allocator_type()) : _Base(__n, __a)
          { _M_finish = uninitialized_fill_n(_M_start, __n, __value); }

push_back时空间分配

vector的动态增加大小,并不是在原空间后接着增加新空间(无法保证原空间后尚有可供配置的空间),而是重新配置一块大小为原来的两倍的空间,然后将原内容拷贝过来并在其之后构造新元素,并释放原空间。因此,对vector进行操作后若引起空间的重新配置,则指向原vector的所有迭代器都失效(前面说过vector的迭代器是普通指针,这里是因空间释放导致指针失效)。
进行push_back()操作时,该函数会检查空间中是否还有未使用的空间,如果有则在其上面构造元素,并调整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);
  }


template <class _Tp, class _Alloc>
void 
vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
  if (_M_finish != _M_end_of_storage) {  //还有备用空间,push_back该段不会执行
    construct(_M_finish, *(_M_finish - 1)); //在备用空间起始处构造一个元素,并以vector最后一个元素作为其初值
    ++_M_finish;//调整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,则配置空间大小为1,否则为原来的两倍
    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);
      ++__new_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;
  }
}

erase操作

erase有两种形式,一种是清处某个位置上的元素,另一种是
清处某个区间上的元素。处理方式都比较简单,将后面的元素覆盖要移除的元素上,并释放多余的空间。vector的clear操作也是通过erase的局部清楚操作实现。


局部空间的清除操作:erase(first, last)
  //清除某一位置的元素
  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);
    destroy(__i, _M_finish);
    _M_finish = _M_finish - (__last - __first);
    return __first;
  }

  void clear() { erase(begin(), end()); }

insert操作

insert操作执行时分为三种情况,一种是插入元素小于插入点后现有元素,另一种是插入元素大于插入点后现有元素,还要就是备用空间不够的情况。下面先放这三种情况的图解,看完图解再看代码会便于理解。


插入元素小于插入点后现有元素

代码实现如下

    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);
插入元素大于插入点后现有元素

代码实现如下

    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);

以上两种情况都是基于备用空间够用的情况下,当备用空间不够用时,操作如下图所示


备用空间不够

代码实现如下

      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);
        __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完整代码实现

void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, const _Tp& __x)
{
  if (__n != 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);
      }
    }
    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);
        __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;

    }
  }
}

总结

vector的基本操作还有很多无法逐一列举,不过从中我们了解到了其动态变化和其迭代器的本质。动态扩展空间是通过重新分配空间,数据复制和释放原空间三步完成。因为vector迭代器是通过普通指针实现,当重新分配空间后,原指针自然也就失效。vector的操作也是在这两个基础上进行相关的实现。

注:文中图片来源于《STL源码剖析》

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容