送给C++开发朋友们:vector 的 源码分析与使用规范

vector概述

vector 是动态空间的 array, 随着其元素的加入,内部机制会自行扩充以容纳新元素,因此,vector 的运用对内存的合理运用和运用的灵活性有很大的帮助。

vector 的实现技术, 关键在于其对大小的控制 以及 重新配置时的数据移动效率

在这里插入图片描述

vector 的原理分析

接下来我们来了解以下 vector 内部我们可访问的成员和成员函数做一下讲述:

  • vector 的嵌套型别名定义(T 代表泛型类型):
  using value_type = T;         
  using pointer = value_type *;
  using iterator = value_type *;
  using reference = value_type &;
  using size_type = size_t;
  using difference_type = ptrdiff_t;

1. vector 的迭代器

vector 维护的是连续线性空间,且 vector 迭代器应需要的操作行为 : operator *, operator ->, operator ++,operator --,operator +, operator -, operator +=, operatpr -=,vector支持随机存取 ,这些普通指针天生都具备,因此 vector 的迭代器是 Random Access iterator:

template<class T, class Alloc = std::allocator<T>>
class MyVector {
 public:
  using value_type = T;
  using pointer = value_type *;
  using iterator = value_type *; //vector 的迭代器是普通指针
  。。。
}

2. vector 的数据结构

vector 所采用的数据结构是非常简单的: 线性连续空间。
使用三个迭代器管理器线性的连续空间startfinish 分别指向配置得来的连续空间中目前已经使用的范围,以迭代器 end_of_storage 指向整块连续空间(含备用空间)的尾端。

  iterator start;           //表示目前使用空间的头部
  iterator finish;          //表示目前使用空间的尾部
  iterator end_of_storage;  //表示目前可用空间的尾部

在这里插入图片描述

容量(capacity)的观念:为了降低空间配置的成本, vector 的实际配置的大小比客户端需求量更大一点, 以备将来可能的扩充。

vector 的性质:容量永远大于等于其 size, 一旦容量等于 size,便是满载,下次再有新增元素,整个vector 就必须另寻其他空间。

3. 利用空间的管理迭代器实现的操作

运用 start , finish, end_of_storage 三个迭代器,便可轻易地提供首尾标示大小容量空容器判断索引( [ size_type n] 和 at( size_type n) )最前端元素,最后端元素值等等操作。

  iterator begin() const { return start; }
  iterator end() const { return finish; }
  size_type size() const { return size_type(end() - begin()); }
  size_type capacity() const { return size_type(end_of_storage - begin()); }
  bool empty() const { return begin() == end(); }
  reference operator[](size_type n) { return *(begin() + n); }
  reference at(size_type n) {
      if (begin() + n >= end())
        throw std::out_of_range("n is out_of_range!\n");
      return *(begin() + n);
  }

  reference front() { return *begin(); }    //返回首元素
  reference back() { return *(end() - 1); }  //返回尾巴元素

4. vector 的构造和内存管理: construct,push_back

首先我们知道,为了完成 容量 这一机制,其内存分配 和 构造元素 的操作一定是分开的,且内存分配大小一定比构造的元素数量要大,仅当内存分配的空间不足以构造元素时,会开辟新空间,转移原元素,释放原空间。
接下来就让我们深入理解这一过程在源码中的体现。

vector 缺省使用 alloc (SGI STL特殊的空间配置器) 作为其内存分配的空间配置器.

这里为了我们的演示,博主使用 标准库 的 std::allocator 作为我们的空间配置器(以元素大小为配置单位)
设置其为静态成员变量 data_allocator

template<class T, class Alloc = std::allocator<T>>
templat<class T, class Alloc> Alloc vector<T,Alloc>::data_allocator;
  • vector 构造时使用 保护成员函数 fill_initialize 来分配和构造指定元素。
    1. 利用 Alloc 分配内存, 并利用 std::uninitialized_fill_n 构造相同的元素放置与内存空间 。
    2. 对 空间管理迭代器(start , finish , end_of_storage )设置初始值
  void fill_initialize(size_type n, const T &value) {
	    start = allocate_and_fill(n, value);
	    finish = start + n;
	    end_of_storage = finish;
  }
    // 配置空间并填满内容
  iterator allocate_and_fill(size_type n, const T &x) {
	    iterator result = data_allocator.allocate(n);
	    std::uninitialized_fill_n(result, n, x);
	    return result;
  }
  • vector 在扩容机制的体现 push_back
  1. 检查是否有备用空间, 如果有直接在备用空间上构造元素, 并调整迭代器 finish,使得 vector 的 size 变大
  2. 如果无备用空间, 就扩充空间(重新配置, 移动数据, 构造原空间),源码中,调用的是 insert_aux(pos, x)来完成操作,我们呢,下面会细讲 其insert_aux(pos, x)
void push_back(const T &x) {
    if (finish != end_of_storage) {
      data_allocator.construct(finish, x);
      ++finish;
    } else {
      insert_aux(end(), x);
    }
 }

解析: insert_aux(iterator position, const T &x)

  • 如果当前 size < 容量,说明不需要开辟新空间,可用直接插入,具体做法为:

    1. 在 finish 位置构造一个与尾后元素相同的元素,并自增 finish (vector size += 1)
    2. 之后利用 copy_backward 将从 finish -2 开始的元素反序拷贝到 finish -1 ,正序会出现错误
      copy_backward 可实现从 2 3 3 -> 2 2 3 的效果,之后再将 2 这个位置替换为 插入的 x。
  • 如果当前 size == 容量,说明必须额外开辟新空间, 新空间为原空间的 2 倍。
    接下来的操作需要 使用 try catch,对异常发生有安全的处理。

    1. 将原空间的数据(插入位置之前的)使用 uninitialized_copy 从原空间拷到新空间中 ,并再构造一个指定的元素,之后将原空间的数据(插入位置之后的元素)再次拷贝到新空间
    2. 发生异常后要对新开辟的内存与新构造的元素全部回收
    3. 最后,使原空间的内存和元素析构和回收, 并设置 (start finish end_of_ storage) 的位置。
template<class T, class Alloc>
void MyVector<T, Alloc>::insert_aux(iterator position, const T &x) {
  if (finish != end_of_storage) {  //还有备用空间
    data_allocator.construct(finish, *(finish - 1));
    ++finish;
    T x_copy = x;
    std::copy_backward(position, finish - 2, finish - 1);
    *position = x_copy;

  } else {
    const size_type old_size = size();
    const size_type len = old_size != 0 ? 2 * old_size : 1;

    iterator new_start = data_allocator.allocate(len);
    iterator new_finish = new_start;

    try {
      new_finish = std::uninitialized_copy(start, position, new_start);
      data_allocator.construct(new_finish, x);
      ++new_finish;
      new_finish = std::uninitialized_copy(position, finish, new_finish);

    } catch (...) {
      destory(new_start, new_finish);
      data_allocator.deallocate(new_start, len);
      throw;
    }

    this->destory(begin(), end());
    deallocate();

    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
  }
}

注意: 所谓动态增加大小,不是指在原空间之后接续新空间(无法保证原空间之后有足够的空间),而是以两倍的原空间大小另外配置一片新的空间,然后将原内容拷贝,并开始在原内容之后构造新元素,并释放原空间。

空间重新配置后,指向原vector 的所有迭代器就都失效了!!!

5. vector 的元素操作: pop_back, erase, clear, insert

vector 支持的元素操作细节我们还是需要讲一讲的,在 standly lippman 的口中我们知道 vector 的很多操作性能上并不友好,反而会伴随重新分配空间或者大量移动操作,接下来我们来看一下其常用操作的源码。

  • pop_ back : 对最后一个元素执行析构操作,并改变空间管理迭代器 finish 位置
  void pop_back() {
    --finish;
    data_allocator.destory(finish);
  }

  • erase :删除某个位置上的元素,或范围内的所有元素

操作的算法思想是将删除位置后的元素拷贝到 从删除位置开始的位置,拷贝完成后,再将拷贝位置后到末尾端的元素全部析构。

template<class T, class Alloc>
typename MyVector<T, Alloc>::iterator MyVector<T, Alloc>::erase(iterator position) {
  if (position + 1 != end())
    std::copy(position + 1, finish, position);
  --finish;
  data_allocator.destroy(finish);
  return position;
}

template<class T, class Alloc>
//typename 要写,因为MyVector<T,Alloc>::iterator 在T没有被推断之前,不清楚iterator到底是什么
typename MyVector<T, Alloc>::iterator MyVector<T, Alloc>::erase(iterator first, iterator last) {
  iterator i = std::copy(last, finish, first);
  destory(i, finish);
  finish = finish - (last - first);
  return first;
}
  • clear :利用erase对所有的元素执行析构
  void clear() { erase(begin(), end()); }
  • insert : 插入操作,很大几率引起空间的重新配置,操作及其复杂
template<class T, class Alloc>
void MyVector<T, Alloc>::insert(iterator position, size_type n, const T &x) {
  if (n != 0) {
    if (size_type(end_of_storage - finish) >= n) {
      //备用空间足够大于等于其新增元素个数
      T x_copy = x;
      const size_type elems_after = finish - position;
      iterator old_finish = finish;
      if (elems_after > n) {
        std::uninitialized_copy(finish - n, finish, finish);
        finish += n;
        std::copy_backward(position, old_finish);
        std::fill(position, position + n, x_copy);
      } else {
        std::uninitialized_fill_n(finish, n - elems_after, x_copy);
        finish += n - elems_after;
        std::uninitialized_copy(position, old_finish, finish);
        finish += elems_after;
        std::fill(position, old_finish, x_copy);
      }
    } else {
      //备用空间小于”新增元素个数” (额外配置内存)
      const size_type old_size = size();
      const size_type len = old_size + std::max(old_size, n);
      // 配置新的vector空间
      iterator new_start = data_allocator.allocate(len);
      iterator new_finish = new_start;
      try {
        //首先将旧 vector 插入点之前的元素全部赋值到新空间
        new_finish = std::uninitialized_copy(start, position, new_start);
        // 以下再将新增元素(初值皆为n)填入新空间
        new_finish = std::uninitialized_fill_n(new_finish, n, x);
        //以下再将旧 vector 的插入点之后的元素复制到新空间
        new_finish = std::uninitialized_copy(position, finish, new_finish);
      } catch (...) {
        destory(new_start, new_finish);
        data_allocator.deallocate(new_start, len);
        throw;
      }
      this->destory(start, finish);
      deallocate();
      start = new_start;
      finish = new_finish;
      end_of_storage = new_start + len;
    }
  }
}

vector 的使用规范

STL中为我们提供了额外的对容器大小管理操作:

c.shrink_to_fit()    //将容量缩小至 size 大小
c.capacity()          //获取当前容量
c.reserve(n)          //分配至少能容纳n个元素的内存空间

  • 如果我们预知我们使用vector 存放多少元素时,我们使用 reserve 预先分配,可以避免因为多次扩容带来的效率问题,

  • 如果我们不用再次存放元素(插入或者添加)我们使用 shrink_to_fit 将额外容量释放掉,保证资源不会倍占用太多。

  • vector 的中间插入操作是非常慢的,(很大几率导致内存重新分配及 内存分配异常)强烈建议避免这些操作。

自己动手实现 MyVector

源码地址(求小星星吖) :GitHub在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/chongzi_daima/article/details/107677100