在读一些开源代码时,总是能见到vector在push_back之前进行reserve的操作,以前一直觉得是多此一举,现在看来是自己才疏学浅罢了。
1. 使用reserve的必要性
众所周知,C++的STL容器的内存大小会随着数据数量的增加而增长。对于vector和string来说,每当须要更多的内存空间时(比如push_back操作),该容器就调用与realloc类似的操作。而这个操作大概如下:
- 为当前容器分配一块新内存,内存大小为当前容量的某个倍数(通常为2倍);
- 将该容器内的全部元素从旧的内存复制到刚才新分配的内存中。
- 析构掉旧内存中的对象。
- 释放旧内存。
这样以来,随着容器的元素个数的增长,重新分配内存的繁琐操作就会不断进行,大大降低程序的效率。而reserve成员函数可以一次性分配制定大小的内存。在举例子之前,先介绍几个与reserve相关的成员函数(就vector和string来说)。
- size():容器元素大小,即容器中有多少个元素。
- capacity():容器的容量,即内存大小,反应了该容器已经分配的内存能够容纳多少个元素。
- resize(Container::size_type n):强行将容器的元素个数设置为n。如果n比当前的大小要小,则容器尾部的元素将会被析构掉。如果n比当前的大小要大,则通过默认构造函数创建的新元素将被加入到容器的末尾,此时如果该容器的容量不够大,就会重新进行内存分配。
- reserve(Container::size_type n):通过重新分配内存强行将容器的容量设置为n,n不小于当前大小。
2. reserve示例
#include <iostream>
#include <vector>
#include <chrono>
using namespace std;
int main()
{
std::chrono::steady_clock::time_point t1, t2;
double dt;
int n = 10000000;
vector<int> vec1, vec2;
t1 = std::chrono::steady_clock::now();
for(int i=0; i<n; i++)
vec1.emplace_back(i);
t2 = std::chrono::steady_clock::now();
dt = std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();
cout << "vec1.size: " << vec1.size() << endl;
cout << "vec1.capacity: " << vec1.capacity() << endl;
cout << "No reserve time: " << dt << endl << endl;
t1 = std::chrono::steady_clock::now();
vec2.reserve(n);
for(int i=0; i<n; i++)
vec2.emplace_back(i);
t2 = std::chrono::steady_clock::now();
dt = std::chrono::duration_cast<std::chrono::duration<double> >(t2 - t1).count();
cout << "vec2.size: " << vec2.size() << endl;
cout << "vec2.capacity: " << vec2.capacity() << endl;
cout << "Use reserve time: " << dt << endl;
return 0;
}
输出结果如下:
vec1.size: 10000000
vec1.capacity: 16777216
No reserve time: 0.165066
vec2.size: 10000000
vec2.capacity: 10000000
Use reserve time: 0.136831
上面的程序我们对vec1没有使用reserve,直接emplace_back,而对vec2先reserve,在emplace_back。为了让结果对比更明显,这里将n设置为10000000。可以看出使用reserve后,向容器中存入数据的速度明显加快。而且不会出现多余的内存空间,vec1的容量多出了6777216。
3. 如何释放容器中多余的内存
上面的vec1多出的内存是一种浪费,就算使用reserve,我们也可能事先不知道有多少个元素,需要多少内存,只能往大了去reserve,那么针对多出来的内存,如何合理的释放呢。
vector有个成员函数clear(),可以清空所有元素。单数clear只能清空元素,不能释放内存。 在《effective STL》中指明,用clear()无法保证内存回收,但是swap技法可以。具体方法如下所示:
vector<int> ivec;
ivec.push_back(1);
ivec.push_back(1);
ivec.push_back(2);
ivec.push_back(2);
vector<int>().swap(ivec); //或者ivec.swap(vector<int>());
这种方法可以完全清空vector中的元素并释放内存。那么我们不想清空元素,只想释放多出来的内存该怎么做呢。
// 方法一
vector<int>(vec1).swap(vec1);
// 方法二(C++11引入)
vec2.shrink_to_fit();
4. 释放容器中多余内存方法验证
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n = 10000000;
vector<int> vec1, vec2;
for(int i=0; i<n; i++)
{
vec1.emplace_back(i);
vec2.emplace_back(i);
}
cout << "vec1.capacity before swap: " << vec1.capacity() << endl;
vector<int>(vec1).swap(vec1);
cout << "vec1.capacity after swap: " << vec1.capacity() << endl << endl;
cout << "vec2.capacity before shrink_to_fit: " << vec2.capacity() << endl;
vec2.shrink_to_fit();
cout << "vec2.capacity after shrink_to_fit: " << vec2.capacity() << endl;
return 0;
}
输出结果如下:
vec1.capacity before swap: 16777216
vec1.capacity after swap: 10000000
vec2.capacity before shrink_to_fit: 16777216
vec2.capacity after shrink_to_fit: 10000000
可见,swap和shrink_to_fit可以达到相同的目的。不过既然C++11了,那还是用shrink_to_fit吧,应该更规范一些。