首页 > STL源码剖析 > STL笔记之vector

STL笔记之vector

vector是初次了解STL接触最多的一个容器,是一种很方便的数组替代品,不需要显示指定容量大小,其内部可以根据需要进行自动扩容操作。也正因为这个特性,每次扩容的时候都会伴随着“配置新空间 / 移动旧数据 / 释放旧空间”的操作,因此是有一定的时间成本的,当然vector提供了reserve接口,如果能够对元素个数有一个大概的了解,那么可以一开始就分配合适的空间。vector的内存空间是连续的,因此对插入元素的操作而言,在vector尾部插入才是合适的选择。

1. _Vector_base 基类
SGI STL的vector拥有一个叫做_Vector_base基类,该类主要定义一个基本框架(start、finish、end_of_storage三个指针),并在构造函数和析构函数中进行空间的分配与回收操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// vector 基类 _Vector_base 定义
template <class _Tp, class _Alloc> 
class _Vector_base {
public:
  typedef _Alloc allocator_type;
  // 获取一个空间配置器对象
  allocator_type get_allocator() const { return allocator_type(); }
  // 默认构造:没有指定初始节点个数
  _Vector_base(const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) {}
  // 构造函数:指定节点个数n
  _Vector_base(size_t __n, const _Alloc&)
    : _M_start(0), _M_finish(0), _M_end_of_storage(0) 
  {
    // 分配 n 个节点所需空间
    _M_start = _M_allocate(__n);    // _M_start  指向起始位置
    _M_finish = _M_start;           // _M_finish 指向其实位置
    _M_end_of_storage = _M_start + __n; // _M_end_of_storage指向内存末尾节点
  }
  // 析构函数:释放内存空间
  ~_Vector_base() { _M_deallocate(_M_start, _M_end_of_storage - _M_start); }
 
protected:
  _Tp* _M_start;            // 指向第一个元素所在的节点
  _Tp* _M_finish;           // 指向最后一个元素所在节点的下一个节点
  _Tp* _M_end_of_storage;   // 可用内存空间的末尾节点
  // 空间配置器
  typedef simple_alloc<_Tp, _Alloc> _M_data_allocator;
  // 分配 n 个节点所需要的空间
  _Tp* _M_allocate(size_t __n)
    { return _M_data_allocator::allocate(__n); }
  // 释放 n 个节点所对应的空间
  void _M_deallocate(_Tp* __p, size_t __n) 
    { _M_data_allocator::deallocate(__p, __n); }
};

2. vector 成员函数分析
迭代器相关函数定义,包括begin(), end() 以及 const版本、reverse版本、reverse const版本:

  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()); }
  // 最大元素个数:超出这个数目将会溢出32位空间
  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(); }

下标访问操作,包括不检查是否越界的[]操作符,以及检查是否越界的at函数:

  // []操作符:non-const / const
  reference operator[](size_type __n) { return *(begin() + __n); }
  const_reference operator[](size_type __n) const { return *(begin() + __n); }
 
  // 如果元素索引越界则抛出异常
  void _M_range_check(size_type __n) const {
    if (__n >= this->size())
      __stl_throw_range_error("vector");
  }
 
  // 通过at访问元素会检查是否越界
  reference at(size_type __n)
    { _M_range_check(__n); return (*this)[__n]; }
  const_reference at(size_type __n) const
    { _M_range_check(__n); return (*this)[__n]; }

构造函数与析构函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  // 无参数的构造函数
  explicit vector(const allocator_type& __a = allocator_type())
    : _Base(__a) {}
  // 指定元素个数n以及元素初始值的构造函数
  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); }
  // 指定元素个数n的构造函数
  explicit vector(size_type __n)
    : _Base(__n, allocator_type())
    { _M_finish = uninitialized_fill_n(_M_start, __n, _Tp()); }
  // 复制构造函数
  vector(const vector<_Tp, _Alloc>& __x) 
    : _Base(__x.size(), __x.get_allocator())
    { _M_finish = uninitialized_copy(__x.begin(), __x.end(), _M_start); }
 
#ifdef __STL_MEMBER_TEMPLATES
  // 迭代器传参构造函数
  template <class _InputIterator>
  vector(_InputIterator __first, _InputIterator __last,
         const allocator_type& __a = allocator_type()) : _Base(__a) {
    typedef typename _Is_integer<_InputIterator>::_Integral _Integral;
    _M_initialize_aux(__first, __last, _Integral());
  }
 
  template <class _Integer>
  void _M_initialize_aux(_Integer __n, _Integer __value, __true_type) {
    _M_start = _M_allocate(__n);
    _M_end_of_storage = _M_start + __n; 
    _M_finish = uninitialized_fill_n(_M_start, __n, __value);
  }
 
  template <class _InputIterator>
  void _M_initialize_aux(_InputIterator __first, _InputIterator __last,
                         __false_type) {
    _M_range_initialize(__first, __last, __ITERATOR_CATEGORY(__first));
  }
 
#else
  vector(const _Tp* __first, const _Tp* __last,
         const allocator_type& __a = allocator_type())
    : _Base(__last - __first, __a) 
    { _M_finish = uninitialized_copy(__first, __last, _M_start); }
#endif /* __STL_MEMBER_TEMPLATES */
  // 析构函数:destroy负责对元素进行析构操作,空间回收由父类负责
  ~vector() { destroy(_M_start, _M_finish); }

需要注意的是第5行所在的构造函数和第20行所在的构造函数可能产生冲突,因为对第20行的构造函数而言,如果first和last都是数值的话,存在两种情况:
1. first和last是指针,迭代器也是指针,也就是first和last确实是迭代器;
2. first是n,last是value,也就是first和last是整形;
这里通过上一篇文章《STL笔记之迭代器》中提到的type_traits技术来进行鉴别,然后通过重载机制选择正确的实现版本。

reserve函数:If n is less than or equal to capacity(), this call has no effect. 只有设定的大小n比旧的容量大时才有作用。

  void reserve(size_type __n) {
    // 如果n比容量要小,那么不进行任何操作
    if (capacity() < __n) {
      // 元素个数
      const size_type __old_size = size();
      // 分配新的空间并进行复制操作
      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;
    }
  }

swap成员函数:实际上是交换了两个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);
  }

同时对全局的swap进行了重载操作:

template <class _Tp, class _Alloc>
inline void swap(vector<_Tp, _Alloc>& __x, vector<_Tp, _Alloc>& __y)
{
  __x.swap(__y);
}

插入以及删除操作:

  // 在指定位置插入一个元素
  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);
    // 指向新插入的元素
    return begin() + __n;
  }
 
  // 删除末尾元素
  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);
    // 析构末尾的元素
    destroy(__i, _M_finish);
    // 调整指针
    _M_finish = _M_finish - (__last - __first);
    return __first;
  }
 
  // resize操作
  void resize(size_type __new_size, const _Tp& __x) {
    // 尺寸变小:删除末尾多余的元素
    if (__new_size < size()) 
      erase(begin() + __new_size, end());
    // 尺寸变大:往末尾插入新元素
    else
      insert(end(), __new_size - size(), __x);
  }
  void resize(size_type __new_size) { resize(__new_size, _Tp()); }
  // 清空所有元素
  void clear() { erase(begin(), end()); }

赋值操作符:

template <class _Tp, class _Alloc>
vector<_Tp,_Alloc>& 
vector<_Tp,_Alloc>::operator=(const vector<_Tp, _Alloc>& __x)
{
  // 如果是自己赋值给自己那么不进行任何操作
  if (&__x != this) {
    const size_type __xlen = __x.size();
    // 如果 new size > cur capacity,则必须分配新的空间
    if (__xlen > capacity()) {
      iterator __tmp = _M_allocate_and_copy(__xlen, __x.begin(), __x.end());
      destroy(_M_start, _M_finish);
      _M_deallocate(_M_start, _M_end_of_storage - _M_start);
      _M_start = __tmp;
      _M_end_of_storage = _M_start + __xlen;
    }
    // 如果new size <= cur size,则复制新的元素,并析构末尾的旧元素
    else if (size() >= __xlen) {
      iterator __i = copy(__x.begin(), __x.end(), begin());
      destroy(__i, _M_finish);
    }
    // 如果new size > cur size 并且容量足够,则直接复制即可
    else {
      copy(__x.begin(), __x.begin() + size(), _M_start);
      uninitialized_copy(__x.begin() + size(), __x.end(), _M_finish);
    }
    // 调整指针
    _M_finish = _M_start + __xlen;
  }
  return *this;
}

3. vector 动态扩容机制
vector的push_back源码如下,当还有可用空间的时候,直接在末尾构造一个新的节点,否则通过调用辅助函数_M_insert_aux来实现。

  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中,仍然会判断空间是否足够,不够的情况下会进行“配置新空间 / 移动旧数据 / 释放旧空间”的操作,空间是成倍增长的,_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));
    // 调整finish指针
    ++_M_finish;
    // 临时对象
    _Tp __x_copy = __x;
    // copy_backward(first, last, result);
    // 复制[first, last) 到 [result - (last - first), result)
    // 即[position, finfish-2)往后移动一个节点
    copy_backward(__position, _M_finish - 2, _M_finish - 1);
    // 插入元素
    *__position = __x_copy;
  }
  // 空间不够用了,需要重新配置
  else {
    // 旧的大小
    const size_type __old_size = size();
    // 如果旧的大小为0,那么新的大小为1
    // 如果旧的大小不为0,那么新的大小为旧的两倍
    const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
    // 分配新的内存区块
    iterator __new_start = _M_allocate(__len);
    iterator __new_finish = __new_start;
    __STL_TRY {
      // 复制旧的区间内容 [start, position)
      __new_finish = uninitialized_copy(_M_start, __position, __new_start);
      // 插入元素到新的区间的position位置
      construct(__new_finish, __x);
      // new finish后移
      ++__new_finish;
      // 复制position之后的元素 [position, 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;
  }
}

vector空间成倍增长示意图如下:
vector空间成倍增长示意图

4. 插入n个元素
如果在某一个位置插入n个元素,那么空间的增长会有一点点不同。
如果空间不够,那么两倍于旧空间大小的尺寸可能也是不够的,因此要做一个判断:
new_size = old_size + max(old_size, n);

template <class _Tp, class _Alloc>
void vector<_Tp, _Alloc>::_M_fill_insert(iterator __position, size_type __n, 
                                         const _Tp& __x)
{
  // 如果n为0那么什么都不做
  if (__n != 0) {
    // 如果剩余空间足够插入n个元素
    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();
      // 新的尺寸 >= 2倍于旧的尺寸
      const size_type __len = __old_size + max(__old_size, __n);
      // 分配空间并调整指针
      iterator __new_start = _M_allocate(__len);
      iterator __new_finish = __new_start;
      __STL_TRY {
        // 复制旧区间[start,position)元素
        __new_finish = uninitialized_copy(_M_start, __position, __new_start);
        // 插入n个x元素的副本
        __new_finish = uninitialized_fill_n(__new_finish, __n, __x);
        // 复制旧区间[position, finish)元素
        __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;
    }
  }
}

STL源码剖析笔记系列
1. STL笔记之空间配置器
2. STL笔记之迭代器
3. STL笔记之vector


觉得文章还不错?点击此处对作者进行打赏!


本文地址: 程序人生 >> STL笔记之vector
作者:代码疯子(Wins0n) 本站内容如无声明均属原创,转载请保留作者信息与原文链接,谢谢!


更多



  1. 本文目前尚无任何评论.
  1. 本文目前尚无任何 trackbacks 和 pingbacks.