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

7
无论我在互联网上阅读哪篇文章,都强烈建议如果我希望我的类与std::vector很好地配合(即,std::vector使用了我的类的移动语义),就应该将移动构造函数声明为“noexcept”(或者noexcept(true))。即使作为一项实验,我将其标记为noexcept(false),为什么std::vector还是使用它了呢?
#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()

为什么?我做错了什么吗?

使用CXX_FLAGS编译的gcc 4.8.2:

--std=c++11 -O0 -fno-elide-constructors
2个回答

12

你没有做错任何事情。

你只是错误地认为push_back必须避免抛出移动构造函数: 在构造新元素时,它并不需要这样做。

唯一必须避免抛出移动构造函数/移动赋值运算符的地方是在重新分配向量时,以避免移动一半的元素和其余元素保持原来的位置。

该函数提供了强烈的异常安全保证:

操作成功或者失败而且没有改变任何东西。


4
如果vector::push_back需要重新分配其存储空间,它首先分配新的内存,然后将新元素的构造移动到最后一个位置。如果这导致抛出异常,则释放新内存,没有任何更改,即使移动构造函数可能会抛出异常,也可以获得强异常安全保证。
如果不抛出异常,则现有元素从原始存储传输到新存储中,在这里移动构造函数的noexcept规范很重要。如果移动可能会抛出并且类型是CopyConstructible,则现有元素将被复制而不是移动。
但在您的测试中,您只关注如何将新元素插入向量中,使用可抛出构造函数始终是可以的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接