10

给定X以下类(明确定义的成员函数以外的特殊成员函数与本实验无关):

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) { std::cout << "X(X&&)" << std::endl; }
};

下面的程序创建一个类型对象的向量X并调整它的大小,以便超出其容量并强制重新分配:

#include <iostream>
#include <vector>

int main()
{
    std::vector<X> v(5);
    v.resize(v.capacity() + 1);
}

由于类X提供了一个移动构造函数,我希望向量的先前内容在重新分配后被移动到新的存储中。令人惊讶的是,情况似乎并非如此,我得到的输出是:

X(X const&)
X(X const&)
X(X const&)
X(X const&)
X(X const&)

为什么?

4

2 回答 2

20

C++11 标准第 23.3.6.3/14 段规定(关于类模板的resize()成员函数):vector<>

备注:如果非由非移动构造函数引发的异常CopyInsertable T 没有任何影响

换句话说,这意味着 for X(which is CopyInsertable)resize()提供了强有力的保证:它要么成功,要么保持向量的状态不变。

为了满足这个保证,实现通常采用复制和交换习惯用法:如果抛出的复制构造函数X,我们还没有改变原始向量的内容,所以保持承诺。

但是,如果向量的先前内容被移动到新存储中而不是被复制并且移动构造函数抛出,那么我们将不可逆转地更改向量的原始内容。

因此,实现将使用复制构造函数X来安全地将向量的内容传输到新存储中,除非知道移动构造函数不会 throw,在这种情况下,从先前的元素移动是安全的。

对 move 构造函数的定义稍作改动X(将其标记为noexcept),事实上,程序的输出现在是预期的。:

struct X
{
    X() { }
    X(int) { }
    X(X const&) { std::cout << "X(X const&)" << std::endl; }
    X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
//         ^^^^^^^^
};
于 2013-03-31T15:26:27.477 回答
5

考虑异常保证:如果在重新分配期间出现异常,则向量必须保持不变。这只能通过复制元素并保留旧集直到整个复制成功来保证。

只有知道移动构造函数不会抛出异常,才能安全地将元素移动到新位置。为此,请声明移动构造函数noexcept

于 2013-03-31T15:30:14.127 回答