STL

《Effective STL》

14《使用reserve来避免不必要的重新分配》

Posted by Liangjf on April 9, 2019

条目十四《使用reserve来避免不必要的重新分配》

使用vector和string的插入元素的时候,我们是不用担心内存问题的(只要不超过容器的max_size)。因为底层有分配子管理内存。在插入元素的时候,内存不够会发生像realloc的过程:

  1. 分配新的内存块,它有容器目前容量的几倍。在大部分实现中,vector和string的容量每次以2为因数增 长。也就是说,当容器必须扩展时,它们的容量每次翻倍。
  2. 把所有元素从容器的旧内存拷贝到它的新内存。
  3. 销毁旧内存中的对象。
  4. 回收旧内存

先看个例子

vector<int> vec;
for(int i = 0; i < 10; i++)
{
    vec.push_back(i)
}

这个过程会发生最少4次重新分配内存的过程。再来看下一个重新分配内存涉及到哪些过程:

  • 1.new操作–>malloc
  • 2.拷贝操作
  • 3.析构对象
  • 4.释放内存

咋一看,一两次还可以接受,当插入的元素越来越多的时候,对性能的消耗是非常客观的。

上面的问题还只是其中一个问题,在插入元素时,重新分配内存会造成迭代器、指针和引用的失效。

为了优化这两个问题,在使用vector和string的时候,可以使用reserve函数。先引出四个关于容器大小和设置的成员函数:

  • size()———————获得容器的元素个数

  • capacity()———获得容器的容量

  • resize()—————强制设置容器的元素个数(参数大于当前大小,调用默认构造函数创建元素填充在尾部。小于当前大小,会析构并销毁多余的元素)

  • reserve()————强制设置容器的容量大小(参数小于现有的容量大小会忽略当前调用,大于会扩容。)

为了避免容器的不必要扩容而造成的消耗,在初始化容器的时候可以通过reserve()设置容器的容量大小,这样在插入元素的是够,只要当前的元素个数小于容量大小,都不会发生重新分配内存。这样也就不会发生迭代器、指针和引用失效等问题,也就没有多次拷贝,析构对象,释放内存的现象发生。

vector vec;
vec.reserve(15);
for(int i = 0; i < 10; i++)
{
    vec.push_back(i)
}

这个过程在reserve后插入元素不会发生重新分配内存过程。因为,插入的元素个数(10)小于容量的大小(15)。

可能有人说,在开始时设置过多的容量,那么不就相当于数组吗?浪费了剩余没用到内存,不用担心这个问题,我们可以等插入元素完毕的时候:

  • 调用sesize()来修剪大小
  • 使用swap()技巧。(vector tmp_vec(vec).swap(vec) 利用容器的拷贝构造不会复制空内存的原理,先创建一个**纯净**的临时容器,然后再交换容器内容,原来的容器就可以成为一个没有多余容量的容器,节省内存哦)