SGISTL源码探究-vector容器(下)

前言

在本小节中,我们将接着上一下节,继续分析关于vector的内存控制,及一些常用操作部分。

vector的内存控制

引入

在上一节中我们给出了一个简单的例子,借助这个例子已经大约明白了vector管理内存的机制。接下来,我们就来看看push_back这些操作。

push_back
void push_back(const T& x) {
  /* 判断是否到vector最大容量
   * 若没有,则构造该对象
   * 若达到了,则调用`insert_aux`函数
   */
  if (finish != end_of_storage) {
    construct(finish, x);
    ++finish;
  }
  else
    insert_aux(end(), x);
}

事实上,发生容量增倍的操作在insert_aux中。

insert_aux
template <class T, class Alloc>
void vector<T, Alloc>::insert_aux(iterator position, const T& x) {
  /* 若还有空间可用,则直接使用
   * 如果没有,则扩充空间
   */
  if (finish != end_of_storage) {
    //构造一个新的元素,并以vector最后一个元素作为初值
    //finish迭代器指向的位置是没有元素的
    //不要忘了[first, finish),在容器中都是这样
    construct(finish, *(finish - 1));
    ++finish;
    T x_copy = x;
    /* 这步操作实现的是将[position, finish -2)的元素从尾到头复制到从finish-1-1指向的位置(依次向后移)
     * 比如 1 2 3 4 5 5
     * 结果就是 1 2 2 3 4 5
     * 然后再将position指向的位置赋成x
     */
    copy_backward(position, finish - 2, finish - 1);
    //赋值
    *position = x_copy;
  }
  else {
    /*进行到这里,代表容量不够了 进行扩容 */
    //此时的size()大小应该与capacity()大小相等
    const size_type old_size = size();
    /* 这里考虑到了vector容量为0的情况
     * 若不为0,则扩充为原来的2倍
     */
    const size_type len = old_size != 0 ? 2 * old_size : 1;
    /* 注意vector并不是在原空间的基础上进行扩充
     * 而是调用空间配置器另外申请一块新的空间
     * 这是为了防止原有空间无法提供这么大的空间
     */
    iterator new_start = data_allocator::allocate(len);
    iterator new_finish = new_start;
    __STL_TRY {
      //将原vector的元素拷贝到新的vector中
      new_finish = uninitialized_copy(start, position, new_start);
      //在新的vector中插入x
      construct(new_finish, x);
      ++new_finish;
      //将插入点position之后元素也拷贝过来
      new_finish = uninitialized_copy(position, finish, new_finish);
    }


#       ifdef  __STL_USE_EXCEPTIONS
    catch(...) {
      //以下是异常情况
      //若发生异常,则销毁拷贝过去的元素
      //最后将申请的空间释放
      destroy(new_start, new_finish);
      data_allocator::deallocate(new_start, len);
      throw;
    }
#       endif /* __STL_USE_EXCEPTIONS */
    /* 一切大功告成之后,将原空间进行销毁
     * 并把start、finish、end_of_storage这几个迭代器指向新的空间
     */
    destroy(begin(), end());
    deallocate();
    start = new_start;
    finish = new_finish;
    end_of_storage = new_start + len;
  }
}

可能你还有一些疑问,那就是为什么insert_aux中,当有容量可用时,为什么不像push_back那样直接构造元素,而要调用copy_backward。这是因为,insert_aux也可能会被insert调用,而insert是指定位置进行插入的。
并且当空间分配成功后,拷贝旧空间的元素时,也要将position之后的元素拷贝到新空间中,这对于push_back操作其实是不必要的,但是同样,insert操作可能会遇到这种情况。

接下来,我们再看一看vectorinsert操作(STL规定插入操作是插入到position的前一个位置)

insert

insert函数其实有众多的重载版本,大致分为:

  • 指定位置插入一个元素
  • 指定位置插入多个元素
  • 以迭代器的范围插入
//指定位置插入x
iterator insert(iterator position, const T& x) {
  size_type n = position - begin();
  if (finish != end_of_storage && position == end()) {
    construct(finish, x);
    ++finish;
  }
  else
    insert_aux(position, x);
  return begin() + n;
}
iterator insert(iterator position) { return insert(position, T()); }
//迭代器范围插入
#ifdef __STL_MEMBER_TEMPLATES
template <class InputIterator>
void insert(iterator position, InputIterator first, InputIterator last) {
  range_insert(position, first, last, iterator_category(first));
}
#else /* __STL_MEMBER_TEMPLATES */
void insert(iterator position,
            const_iterator first, const_iterator last);
#endif /* __STL_MEMBER_TEMPLATES */
//在pos前插入n个x
void insert (iterator pos, size_type n, const T& x);
void insert (iterator pos, int n, const T& x) {
  insert(pos, (size_type) n, x);
}
void insert (iterator pos, long n, const T& x) {
  insert(pos, (size_type) n, x);
}

接下来,我们来分析这三种情况:

1. 指定位置插入
iterator insert(iterator position, const T& x) {
  size_type n = position - begin();
  /* 如果此时有足够的空间并且插入的元素在末端
   * 就直接在末端插入新元素
   * 如果没有足够空间或插入的元素不在尾部
   * 则调用insert_aux进行插入(插入的元素不在尾部会走if分支,容量不够走else分支)
   */
  if (finish != end_of_storage && position == end()) {
    construct(finish, x);
    ++finish;
  }
  else
    insert_aux(position, x);
  //返回指向插入元素的迭代器
  return begin() + n;
}
2. 范围插入

根据条件编译分不同的函数,但是实现都是一样的,range_insert和下面这个insert的代码一致,就拿其中一个分析就行了。

template <class T, class Alloc>
void vector<T, Alloc>::insert(iterator position,
                              const_iterator first,
                              const_iterator last) {
  if (first != last) {
    size_type n = 0;
    //计算出两个迭代器之间的距离(即元素个数)
    distance(first, last, n);
    //容量够用的情况
    if (size_type(end_of_storage - finish) >= n) {
      const size_type elems_after = finish - position;
      iterator old_finish = finish;
      /* 若插入点之后的元素个数大于新增元素个数
       * 那么我们就可以腾出n个位置给新增元素
       * 如果插入点之后的元素个数小于新增元素个数
       * 则先将[first + elems_after, last)的元素拷到finish之后
       * 然后将[position, old_finish)的元素拷贝到finish之后,腾出elems_after个位置
       * 最后将[first, first + elems_after)的元素拷贝到腾出的位置中
       */
      if (elems_after > n) {
        //将[finish-n, finish)范围内的元素移动到finish之后
        //目的在于腾出n个位置给新元素
        uninitialized_copy(finish - n, finish, finish);
        finish += n;
        //将[position, old_finish - n)的元素依次后移
        copy_backward(position, old_finish - n, old_finish);
        //将[first, last)拷贝进position指向的位置
        copy(first, last, position);
      }
      else {
        //这个插入操作其实是分了两部分的
        //其中一部分是将[first+elems_after, last)的元素拷贝到finish之后,尾部假定为new_finish
        //而[first, first+elems_after)这一部分则先让原来的元素腾到new_finish之后,再复制到腾出的地方
        uninitialized_copy(first + elems_after, last, finish);
        finish += n - elems_after;
        uninitialized_copy(position, old_finish, finish);
        finish += elems_after;
        copy(first, first + elems_after, position);
      }
    }
    else {
      //以下是空间不足需要重新分配的情况
      const size_type old_size = size();
      const size_type
      //此时并没有直接分配old_size的两倍
      //这是考虑到分了old_size的两倍之后,n过于大,容量还是不够的情况
      //所以取两者的最大值
      //再不济,也要把插入n个元素装下
      len = old_size + max(old_size, n);
      iterator new_start = data_allocator::allocate(len);
      iterator new_finish = new_start;
      __STL_TRY {
        /* 因为是插入到position之前
         * 所以需要分成三部将元素拷贝到新空间中
         * 第一部分:[start, position)
         * 第二部分:[first, last)
         * 第三部分:[position, finish)
         */
        new_finish = uninitialized_copy(start, position, new_start);
        new_finish = uninitialized_copy(first, last, new_finish);
        new_finish = uninitialized_copy(position, finish, new_finish);
      }
#         ifdef __STL_USE_EXCEPTIONS
      catch(...) {
      //异常处理
      destroy(new_start, new_finish);
      data_allocator::deallocate(new_start, len);
      throw;
      }
#         endif /* __STL_USE_EXCEPTIONS */
      //释放原空间,并调整迭代器
      destroy(start, finish);
      deallocate();
      start = new_start;
      finish = new_finish;
      end_of_storage = new_start + len;
    }
  }
}

容量够用的情况中又分成的elems_after > n或者elems_after < n这两种情况可能有点不好理解,最好自己画一下图有助于理解。

3. 插入n个元素

根据了n的类型不同进行了重载,不过最后都将n强转成size_type类型进行处理。
该函数和迭代器范围插入很相似,代码的逻辑几乎一模一样,除了迭代器范围插入时,插入新元素是调用copy拷贝,而插入n个元素这类是调用fill之外,并没有什么不一样,这里就不再贴代码占篇幅了。

clear

vector中的clear函数并不负责回收vector的空间,而是负责销毁vector所存的元素。
它的源码如下:

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

其实就是调用了erase函数。那我们接着来看erase函数。它有重载的版本。

//删除position迭代器指向的元素
iterator erase(iterator position) {
  /* 若position指向的不是最后一个元素
   * 那么直接将后面的元素向前移动就完事
   * 如果是最后一个,那就更简单了,直接将finish指向该元素,然后删除
   */
  if (position + 1 != end())
    copy(position + 1, finish, position);
  --finish;
  destroy(finish);
  return position;
}
//删除[first, last)范围内的元素
iterator erase(iterator first, iterator last)
{
  //将last之后的元素拷贝到first的位置,覆盖需要删除的元素
  iterator i = copy(last, finish, first);
  //当last-first的值(需要删除的元素个数)大于finish-last的值时,需要删除的元素没有被覆盖完,则调用destroy进行析构
  destroy(i, finish);
  //调整finish
  finish = finish - (last - first);
  return first;
}

其他操作

关于vector的操作确实挺多,以上也只列举了一些比较重要的。
下面我们再看几个操作符重载的函数以及reserveresize函数。

重载[]
reference operator[](size_type n) { return *(begin() + n); }
重载==
template <class T, class Alloc>
inline bool operator==(const vector<T, Alloc>& x, const vector<T, Alloc>& y) {
  return x.size() == y.size() && equal(x.begin(), x.end(), y.begin());
}

可以看到两个vector容器是否相等需要满足两个条件:

  1. 元素个数相等
  2. 元素的值相等
重载=
template <class T, class Alloc>
vector<T, Alloc>& vector<T, Alloc>::operator=(const vector<T, Alloc>& x) {
  //如果自己对自己赋值,那直接返回就行了
  if (&x != this) {
    /* 判断自身的容量大小是否能存入x
     * 如果不能,分配一个跟x大小相同的空间,并将x拷贝进去
     * 然后销毁原空间
     * 再把迭代器指向新的空间
     */
    if (x.size() > capacity()) {
      iterator tmp = allocate_and_copy(x.end() - x.begin(),
                                       x.begin(), x.end());
      destroy(start, finish);
      deallocate();
      start = tmp;
      end_of_storage = start + (x.end() - x.begin());
    }
    /* 现有元素足够容下x
     * 直接将x的元素拷贝过来
     * 将没有被覆盖的元素析构掉
     */
    else if (size() >= x.size()) {
      iterator i = copy(x.begin(), x.end(), begin());
      destroy(i, finish);
    }
    /* 这种情况是现有元素的个数容不小x
     * 但是总容量可以容下x
     * 所以就需要把现有的元素全部覆盖了,然后把x剩下的元素拷贝到未使用的空间中去
     */
    else {
      copy(x.begin(), x.begin() + size(), start);
      uninitialized_copy(x.begin() + size(), x.end(), finish);
    }
    //最后统一调整finish
    finish = start + x.size();
  }
  //返回赋值后的vector
  return *this;
}

重载赋值操作符可能要稍微麻烦一点,不过比起insert之类的还是比较简单了。
最后再介绍两个函数reserveresize

reserve

该函数的作用是将总容量设置为n

void reserve(size_type n) {
  if (capacity() < n) {
    const size_type old_size = size();
    //分配n大小的空间,将原来的元素拷贝过去
    iterator tmp = allocate_and_copy(n, start, finish);
    //删除原空间的元素
    destroy(start, finish);
    //释放原空间
    deallocate();
    //调整迭代器
    start = tmp;
    finish = tmp + old_size;
    end_of_storage = start + n;
  }
}
resize

该函数的作用就是调整vector的大小。

void resize(size_type new_size, const T& x)
{
  /* 如果要求调整到的大小比原来的大小小
   * 则需要将超出的部分删掉
   * 否则将填入新的元素,初值为x
   */
  if(new_size < size())
    erase(begin() + new_size, end());
  else
    insert(end(), new_size - size(), x);
}

小结

在本小节中我们分析了push_backinsert及一些操作符还有reserveresize等函数。还有一些其他的操作不需要一一列举,感兴趣的可以自己去看代码,没有特别的复杂的了。
还需要特别注意的一点就是关于迭代器的失效问题,如果vector扩容了,那么会导致原来的迭代器都会失效,了解了vector源码之后,对这些特性应该也能理解。
关于vector的分析就到此结束,相信你对vector的内部机制有了更深刻的了解。接下来是list容器。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值