7

无论我在互联网上的何处阅读,强烈建议如果我希望我的类能够很好地工作std::vector(即我的类中的移动语义被使用std::vector),我应该将移动构造函数称为“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
4

2 回答 2

12

你没有做错什么。

您只是错误地认为push_back必须避免抛出 move-ctor:它没有,至少对于构造新元素。

唯一必须避免抛出 move-ctors / move-assignments 的地方是重新分配向量,以避免移动一半元素,而其余元素则在其原始位置。

该函数具有很强的异常安全保证:

操作要么成功,要么失败并且没有任何改变。

于 2014-10-06T20:19:06.073 回答
4

如果vector::push_back需要重新分配其存储空间,它首先分配新内存,然后 move 将新元素构造到最后一个位置。如果抛出的新内存被释放,并且没有任何改变,那么即使移动构造函数可以抛出,您也可以获得强大的异常安全保证。

如果它不抛出,现有元素将从原始存储转移到新存储,就是noexcept移动构造函数的规范重要的地方。如果移动可能会抛出并且类型为 CopyConstructible,则现有元素将被复制而不是移动。

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

于 2014-10-07T11:01:26.773 回答