给定下面的类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
提供了一个移动构造函数,我希望在重新分配后,向量的先前内容将移动到新存储中。相当令人惊讶的是,that does not seem to be the case,我得到的输出是:
X(X const&)
X(X const&)
X(X const&)
X(X const&)
X(X const&)
为什么吗
答案 0 :(得分:17)
C ++ 11标准的第23.3.6.3/14段指定(关于resize()
类模板的vector<>
成员函数):
备注:如果除了非
CopyInsertable T
的移动构造函数之外引发异常,则没有效果。
换句话说,这意味着对于X
(CopyInsertable
),resize()
提供strong guarantee:它要么成功要么保持向量状态不变
为了满足这种保证,实现通常采用copy-and-swap idiom:如果X
的复制构造函数抛出,我们还没有改变原始向量的内容,所以保留了承诺。
但是,如果向量的先前内容移动到新存储中而不是被复制并且移动构造函数抛出,那么我们将不可逆转地改变原始存储载体的内容。
因此,实现将使用X
的复制构造函数将向量的内容安全地传输到新存储,除非已知移动构造函数不抛出,在这种情况下它从以前的元素移动是安全的。
对X
的移动构造函数(将其标记为noexcept
)的定义稍作更改,实际上是the output of the program is now the expected one。:
struct X
{
X() { }
X(int) { }
X(X const&) { std::cout << "X(X const&)" << std::endl; }
X(X&&) noexcept { std::cout << "X(X&&)" << std::endl; }
// ^^^^^^^^
};
答案 1 :(得分:5)
考虑异常保证:如果在重新分配期间出现异常,则向量必须保持不变。只有通过复制元素并保留旧集才能保证这一点,直到整个副本成功为止。
只有当您知道移动构造函数没有抛出时,才能安全地将元素移动到新位置。为此,请声明移动构造函数noexcept
。