STL浅析——序列式容器vector的构造和内存管理: constructor() 和 push_back()

Posted Forever-Road

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STL浅析——序列式容器vector的构造和内存管理: constructor() 和 push_back()相关的知识,希望对你有一定的参考价值。

  咱们先来做一个测试capacity是容器容量,size是大小:

#include <iostream>
#include <vector>
using namespace std;

int main(){
    vector<int> result;
    for (int i = 0; i < 17; i++)
    {
        result.push_back(i);
        printf("element count:  %d\\t", result.size());
        printf("capacity:  %d\\t", result.capacity());
        printf("first element\'s address:  %x\\n", result.begin());
    }
    return 0;
}

  运行结果:

  

  可以观察到每次容器满了需要扩容的时候,容量总是呈现两倍增长,而且每次扩容,容器第一个元素所在地址都会发生改变,由此我们知道,容器的扩容时实际是另外寻找一片更大的空间,VS的如下:

  

  扩容的倍数不一样VS为1.5倍扩容,最好的扩容倍数是黄金分割比,1.618倍,当然也不可能那么精确。。。

  vector 缺省使用 alloc (实际上是 宏__STL_DEFAULT_ALLOCATOR(_Tp) 的typedef,但是该宏本质还是第二级空间配置器最终封装而成的 alloc) 作为空间配置器,并据此定义了一个 _Base。

class vector : protected _Vector_base<_Tp, _Alloc> 
{
...
private:
      typedef _Vector_base<_Tp, _Alloc> _Base;
...
//以前是:
    typedef simple_alloc<value_type, Alloc> data_allocator;
//最新版本是进行了进一步封装,分开了基本参数和泛型操作函数
}

  vector 提供了许多constructors,其中一个允许我们指定空间大小与初值:

  
//就是这个,允许我们指定空间大小和初值
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); } //上面 uninitialized_fill_n 这个函数,藏得很深在stl_uninitialized.h里面,实际该函数还是对fill函数的一次封装
//最终调用的是fill函数,
在 stl_alogbase.h 这个头文件下面,如下,
//不过这个文件夹下面有很多fill函数,我想应该是uninitialized_fill_n的作用还包括型别判断,根据型别不同调用不同的fill函数
void fill(_ForwardIter __first, _ForwardIter __last, const _Tp& __value) {
  __STL_REQUIRES(_ForwardIter, _Mutable_ForwardIterator);
  for ( ; __first != __last; ++__first)
    *__first = __value;
}

  我们以 push_back() 函数将新元素插入vector尾端时,该函数首先检查是否还有备用空间,如果有则直接在备用空间上构造元素,并调整迭代器finish,使得vector变大。如果没有备用空间就扩充:

  void push_back(const _Tp& __x) {
    if (_M_finish != _M_end_of_storage) {
      construct(_M_finish, __x);  //直接placement构造元素,移动_M_finish指针
      ++_M_finish;
    }
    else
      _M_insert_aux(end(), __x);  //重新构造整个vector并插入元素
  }
 //表示不懂为啥要重载一次push_back()。。。
void push_back() { if (_M_finish != _M_end_of_storage) { construct(_M_finish); ++_M_finish; } else _M_insert_aux(end()); }

  至于 _M_insert_aux 函数,有点复杂,但是也不难理解,如下:

vector<_Tp, _Alloc>::_M_insert_aux(iterator __position, const _Tp& __x)
{
  if (_M_finish != _M_end_of_storage)  //有备用空间 
 {
  //在备用空间起始处构造一个元素,并以vector最后一个元素为其初始值 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
  {
    //原vector内容拷贝到新vector __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; } }

  因此可以明白,对 vector 的任何操作,一旦引起空间重新配置,则指向原 vector 的所有迭代器都会失效。

以上是关于STL浅析——序列式容器vector的构造和内存管理: constructor() 和 push_back()的主要内容,如果未能解决你的问题,请参考以下文章

容器元素增删内存变化浅析

一文带你认识STL序列式容器--list

C++/STL0.容器概述

STL学习笔记--4序列式容器之vector

STL 源码剖析读书笔记四:序列式容器之 dequestackqueue

STL之vector