无论我在互联网上读到什么,强烈建议如果我希望我的类能很好地与std::vector
一起工作(即std::vector
使用了类中的move语义),我应该将move构造函数delcare为'noexcept'(或noexcept(true)
)。
为什么std::vector
使用它,尽管我把它标记为noexcept(false)
作为实验
#include <iostream>
#include <vector>
using std::cout;
struct T
{
T() { cout <<"T()n"; }
T(const T&) { cout <<"T(const T&)n"; }
T& operator= (const T&)
{ cout <<"T& operator= (const T&)n"; return *this; }
~T() { cout << "~T()n"; }
T& operator=(T&&) noexcept(false)
{ cout <<"T& operator=(T&&)n"; return *this; }
T(T&&) noexcept(false)
{ cout << "T(T&&)n"; }
};
int main()
{
std::vector<T> t_vec;
t_vec.push_back(T());
}
输出:
T()
T(T&&)
~T()
~T()
为什么?我做错了什么?
在gcc 4.8.2上编译,CXX_FLAGS设置为:
--std=c++11 -O0 -fno-elide-constructors
您没有做错任何事。
您只是错误地认为push_back
必须避免抛出move ctor:它没有,至少在构造新元素时是这样。
唯一必须避免抛出移动因子/移动赋值的地方是向量的重新分配,以避免移动一半的元素,而其余元素则在其原始位置。
该功能具有强大的异常安全保障:
操作要么成功,要么失败,什么都没有改变。
如果vector::push_back
需要重新分配其存储,它首先分配新内存,然后将构造的新元素移到最后一个位置。如果抛出新内存被释放,并且没有任何变化,那么即使move构造函数可以抛出,也可以获得强大的异常安全保证。
如果不抛出,则现有元素将从原始存储转移到新存储,此处是move构造函数的noexcept
规范的重要位置。如果移动可能引发,并且类型为CopyConstructable,则将复制现有元素,而不是移动。
但在测试中,您只关注如何将新元素插入到向量中,并且在该步骤中使用抛出构造函数总是可以的。