我想让我清楚一下移动语义。我正在关注Bjarne Stroustrup第4版的例子,我真的输了。
他说,当很多元素(在类向量中)所以移动语义是解决方案时,对象的副本可能很昂贵。
想想:
矢量结果= vector1 + vector2 + vector3;
顺序可能是错误的,但它会(vector2 + vector3)生成部分结果result1,而result1 + vector1生成结果;
我重载了operator +:
Vector operator+(const Vector &a,const Vector &b)
{
if(a.size()!=b.size()) {
throw length_error{"Length of vectors must match for summing"};
}
Vector result(a.size());
cout << "Intermediate result for sum created" << endl;
for(long unsigned int i=0; i<result.size();i++){
result[i] = a[i] + b[i];
}
return result;
}
我还创建了一个移动构造函数:
Vector::Vector(Vector&& orig) noexcept
:elem{orig.elem},
sz{orig.sz}
{
cout << "Move constructor called" << endl;
orig.elem = nullptr; // We own the array now
orig.sz = 0;
}
因此不执行复制操作。但有一些我不明白的事情。一个与c ++相关,另一个与c ++ 11相关。
首先:
正如您所看到的,当operator +退出时,应该销毁Vector结果。但是在将内容复制到另一个vector实例之后。我称之为部分结果1。
这种情况从未发生过,程序的输出是这样的:
但是直到程序结束,我才看到第二部分结果被破坏了。我也没有看到任何复制操作(我已经完成了operator =和复制构造函数)。
所以我不明白。如果变量Vector结果的范围在operator +内,则必须将内容复制到另一个变量中。那没用。看起来总和的最后结果只是分配给总和的Vector结果。
第二
不执行移动操作。我不能让移动语义在这里工作。我看了http://gcc.gnu.org/bugzilla/show_bug.cgi?id=52745然后应用了什么,但无论如何都没有移动操作。
每一个结果都是对的。
很清楚,有些事我做错了。
你可以从这里获取整个代码(使用autotools config + eclipse):
http://static.level2crm.com/cx11-learn-20140218.tar.bz2
有人能解释一下这里发生了什么吗?
致以最诚挚的问候,
答案 0 :(得分:2)
首先,这里没有移动建筑。有NRVO发生(根据你的问题的评论),但如果你想证明移动建设发生,你应该尝试复制一个临时的。尝试重新实现您的运算符,如下所示:
// Note that the first parameter is taken by value.
Vector operator+(Vector a, const Vector &b)
{
cout << "Intermediate result for sum created or moved from a" << endl;
// NOTE: Could be implemented as operator+= and written a += b.
if(a.size()!=b.size()) {
throw length_error{"Length of vectors must match for summing"};
}
for(long unsigned int i=0; i<a.size();i++){
a[i] += b[i];
}
// END NOTE
return a;
}
请注意,您的运营商现在需要本地“副本”作为第一个参数。在第一个总和上,由于Vector&
将作为参数提供(对象具有名称),因此将调用复制构造函数并使用新缓冲区创建对象(您使用{{1}进行的操作}局部变量)。然后,将形成一个临时值,因此在第二次调用result
时,将提供operator+
作为第一个参数,并且将调用move-constructor,从而有效地窃取临时缓冲区。
如果根据注释阻止构造函数省略,则应该能够看到一些调用的构造函数。
编辑:更详细的解释为什么你不会在你的情况下采取行动。简短的版本是C ++不做魔法:)
在链接添加的情况下移动的目的是避免由于临时创建而重复创建Vector&&
的缓冲区。而不是丢失缓冲区来破坏临时工,目的是重用临时缓冲区来存储结果。
在您的情况下,您甚至没有尝试复制Vector
的缓冲区。您在此行上明确创建了一个新缓冲区:
Vector
你说“请为结果创建一个合适大小的矢量”,并为每次添加做一次。如果你知道Vector result(a.size());
将很快死亡,你最好说“请偷走a
的缓冲区”。写道:
a
但为了实现这一点,Vector result(a);
必须引用即将死亡的对象,这意味着a
必须是a
类型。完成此操作后,Vector&&
已包含result
的值,因此您只需添加a
元素的值即可。
b
(用for(long unsigned int i=0; i<result.size();i++){
result[i] += b[i];
}
表达+
是惯用的)
因此,为了证明移动构造,您应该定义+=
,其签名为:
operator+
但这不适用于第一次添加,因为它不是向另一个命名对象添加临时值,而是添加两个命名对象。你应该有Vector operator+(Vector&& a, const Vector &b) {
if(a.size()!=b.size()) {
throw length_error{"Length of vectors must match for summing"};
}
Vector result(a);
cout << "Intermediate result for sum moved from a" << endl;
for(long unsigned int i=0; i<result.size();i++){
result[i] += b[i];
}
return result;
}
的重载来处理左值引用:
operator+
请注意,两个版本共享相同的文本。这带来了另一种习惯做法:让编译器通过要求Vector operator+(const Vector& a, const Vector &b) {
if(a.size()!=b.size()) {
throw length_error{"Length of vectors must match for summing"};
}
Vector result(a); // In this case, this is a copy
cout << "Intermediate result for sum created" << endl;
for(long unsigned int i=0; i<result.size();i++){
result[i] += b[i];
}
return result;
}
的左侧按值传递来决定是执行移动还是复制,只需要一次重载。 (这是我答案开头的版本。