先说结论:
1、vector push_back时,当置的元素个数超过其容量(capacity),那么vector会分配一个新的存储空间并且将原空间内容转移过去,若自定义的移动构造函数不是noexcept的,那么由于push_back需要保证强异常安全性,那么C++11及以上就会采用拷贝复制的方式,而若移动构造函数是noexcept的,那么C++11会开启优化,将原空间内容通过移动而非复制的方式转移到新空间,这样会大大提升速度。
2、push_back 一个右值引用后会通过移动构造直接将右值的内容移动到vecter对应存储空间中,这个操作与移动构造是否是noexcept的无关。
3、编译器自动生成的移动构造函数也是noexcept的。
下面实测
先定义一个类:
class A {
public:
A (int x_arg) : x (x_arg) {
std::cout << "A (x_arg)\n";
}
A () { x = 0; std::cout << "A ()\n"; }
A (const A &rhs) {
x = rhs.x;
ss = rhs.ss;
std::cout << "A (A &)\n";
}
A (A &&rhs) {
x = rhs.x;
ss = std::move(rhs.ss);
std::cout << "A (A &&)\n";
}
// 自定义了析构 不会自己生成默认移动构造
~A() {
std::cout << "~A ()\n";
}
private:
int x;
string ss = string(100, 'a');
};
A的移动构造是没有noexcept的,那么接着给一个A的vector push_back一个右值引用,看看会发生什么…
void test_emplace_back_4()
{
std::vector<A> a;
A obj(1);
a.push_back(std::move(obj));
}
输出结果如下:
A (x_arg)
A (A &&)
~A ()
~A ()
可见结论2,push_back 一个右值引用后会通过移动构造直接将右值的内容移动到vecter对应存储空间中,并且这个操作与移动构造是否是noexcept的无关。
接着验证结论1,测试push_back 300000个元素的耗时:
void test_emplace_back_4()
{
std::vector<A> a;
std::cout << "call push_back4:\n";
auto start = system_clock::now();
for (int i = 0; i < 300000; i++)
{
A obj(1);
a.push_back(std::move(obj)); // 触发移动构造
// copy constructor to vector
}
auto end = system_clock::now();
auto duration = duration_cast<milliseconds>(end - start);
cout << "time duration ms: " << duration.count() << endl;
}
当移动构造是 noexcept 的时候,耗时约为65ms。
当移动构造是非 noexcept 的时候,耗时约为105ms。
提升显而易见。
接下来将自定义的析构函数、移动构造、拷贝构造全部删除(析构、复制、移动都会影响系统自动生成移动操作),采用编译器自动生成的默认移动构造函数,耗时与自定义的noexcept移动构造一样,因此,编译器自动生成的默认移动构造函数也是noexcept的。