为什么std::vector使用move构造函数,尽管声明为noexcept(false)



无论我在互联网上读到什么,强烈建议如果我希望我的类能很好地与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,则将复制现有元素,而不是移动。

但在测试中,您只关注如何将新元素插入到向量中,并且在该步骤中使用抛出构造函数总是可以的。

最新更新