我读了这篇文章 cppnext 隐式移动,但我不明白这个问题:
#include <iostream>
#include <vector>
struct X
{
// invariant: v.size() == 5
X() : v(5) {}
~X()
{
std::cout << v[0] << std::endl;
}
private:
std::vector<int> v;
};
int main()
{
std::vector<X> y;
y.push_back(X()); // X() rvalue: copied in C++03, moved in C++0x
}
在MSVC2010下,运行时没有错误...谁能帮我?
这篇文章中有这样一句话:
这里的关键问题是,在 C++03 中,X 有一个不变性,即其 v 成员始终有 5 个元素。X::~X() 依靠该不变量,但是 新引入的移动构造函数从 V 移动,从而设置 它的长度为零。
我不明白为什么因为我们试图移动 X,v
长度将为零
本文试图说明的问题是,当X
在push_back中移动时,不变量被破坏了。临时X
的向量v
在移动后将为空,因此析构函数调用将调用未定义的行为。可能是因为编译器没有实现移动语义,或者因为未定义的行为不会导致任何可察觉的运行时错误。
您可以使用这个简单的程序检查此行为,我们在其中显式移动X
实例:
#include <vector>
#include <iostream>
struct X {
X() : v(5) {}
std::vector<int> v;
};
int main() {
X x0;
std::cout << x0.v.size() << ", ";
X x1 = std::move(x0);
std::cout << x0.v.size() << "n";
}
在 GCC 4.7 上,这些产生
5, 0
这来自std::vector
的移动构造函数,该构造函数由X
的隐式生成构造函数使用。您可以直接检查std::vector
:
int main() {
std::vector<int> v0(5);
std::cout << v0.size() << ", ";
std::vector<int> v1 = std::move(v0);
std::cout << v0.size() << "n";
}
这将产生相同的输出。
现在,在您引用的示例中,用户定义的析构函数的存在意味着没有隐式生成的移动构造函数,因此X
不会在push_back
中移动。
我不明白为什么因为我们尝试移动 X,v 长度将为零
好吧,首先,我们不会尝试在push_back调用中移动临时X()
。我们确实移动了它,或者我们不移动[*]。
如果我们要移动它(使用隐式生成的移动构造函数),则移动其数据成员v
。这篇文章的假设是,从向量移动会让它为空,尽管我不记得这是必需的还是典型的。但它确实会让v
处于无法保证您可以访问其以前元素的状态。
请记住,移动的全部意义在于它从源传输昂贵的复制资源。因此,临时对象不再保证具有其包含 5 个元素的内部数组,因为移动的目的地已经采取了它。
至于为什么它在 MSVC 2010 中工作 - 请记住,此代码在文章中作为"调整 #1 - 析构函数抑制隐式移动"上方的示例提供。C++11 实际上确实包含此调整 - 如果存在用户定义的析构函数 (12.8/9),则不会生成隐式移动构造函数。所以临时的不是移动的,而是复制的。
[*] 移动或不移动,没有尝试。是否移动它不取决于实现,标准对此进行了定义。
实现auto=generated move构造函数,所以即使你的代码会生成一个,你也不会在那里看到它。因此,与往常一样,"随机实现X做了Y"是一个毫无意义的陈述。
> 在VS2010中,临时将被复制,然后销毁。使用移动语义,临时对象将仅用于向量。