09-03-2020, 10:15 AM
代码:
#include <iostream>
#include <vector>
using namespace std;
class Base
{
int i;
public:
Base(int i_): i(i_) { cout << i << endl; }
Base(const Base &b): i(b.i) { cout << "Copy " << i << endl; }
~Base() { cout << "Destruct " << i << endl; }
};
class VecBase
{
vector<Base> v;
public:
void push(int i) { v.push_back(Base(i)); }
void emplace(int i) { v.emplace_back(i); }
};
int main()
{
VecBase v;
v.push(1);
v.emplace(2);
}
为了观察std::vector中push_back和emplace_back的区别,我写了以上程序。根据输出发现,Base(1)被构造了3次,其中复制构造函数调了2次,Base(2)被构造了1次,没调用复制构造函数。那么Base(1)的复制构造函数被调了2次呢?
首先是 push_back 和 emplace_back 的区别,emplace_back是在 vector 中原地构造,所以没有复制的过程,而用 push_back 的时候,首先在 VecBase::push() 中用 Base(i) 构造了一次,然后在 std::vector<>::push_back() 中复制了一次。
那么还有一次复制构造函数来自哪里?注意到 std::vector 为了支持任意大小,在增加元素的时候会出现扩容操作,而扩容的方法是重新分配空间,然后把数据复制过去,因此会在复制的过程中调用复制构造函数。
为了验证这个想法,可以多往 std::vector 里面增加元素,就是在以上程序中多加 v.emplace(...).
代码:
int main()
{
VecBase v;
for (int i = 0; i <= 16; i++) {
v.emplace(i);
}
}
在这段程序中,可以发现当 i>0 时,当 i 达到 2^n (n=0,1,2,3,4) 的时候,调用完 Base(i) 构造函数之后,就会对所有小于i的对象逐个执行复制构造函数,然后逐个执行析构函数。这同时可以看出,libstdc++对vector的扩容方式是在放不下数据的时候,对空间进行倍增。
总结一下,如果不想对存在 vector 里面的对象调用太多复制构造函数,就不要经常增大 vector 的容量,否则 vector 容量倍增的时候会对其中的所有对象调复制构造函数。可以先用 std::vector<>::reserve() 预留空间,再做 emplace_back 操作。其他解决方案包括在 vector 中存放指针,这样在容量倍增的时候只复制指针而不是对象本身。
此外,在考虑 move 构造函数的时候,可以发现
代码:
v.push_back(Base(i));
代码:
Base t(i); v.push_back(t)