为什么返回时调用的是拷贝构造函数而不是移动构造函数?
Why is the copy constructor called instead of the move constructor when returning?
假设我有 class MyClass
具有正确的移动构造函数并且其复制构造函数被删除。现在我像这样返回 class:
MyClass func()
{
return MyClass();
}
在这种情况下,移动构造函数在返回 class 对象时被调用,一切都按预期进行。
现在假设 MyClass
有一个 <<
运算符的实现:
MyClass& operator<<(MyClass& target, const int& source)
{
target.add(source);
return target;
}
当我更改上面的代码时:
MyClass func()
{
return MyClass() << 5;
}
我收到编译器错误,无法访问复制构造函数,因为它已被删除。但是为什么在这种情况下完全使用复制构造函数?
您的运营商 return MyClass&
。所以你 return 是左值,而不是可以自动移动的右值。
您可以依靠有关 NRVO 的标准保证来避免复制。
MyClass func()
{
MyClass m;
m << 5;
return m;
}
这将完全消除或移动对象。都是因为它是一个函数局部对象。
另一种选择,当您尝试在右值上调用 operator<<
时,是提供处理右值引用的重载。
MyClass&& operator<<(MyClass&& target, int i) {
target << i; // Reuse the operator you have, here target is an lvalue
return std::move(target);
}
这将使 MyClass() << 5
本身格式正确(请参阅其他答案以了解为什么不是),并且 return 可以构造 return 对象的 xvalue。尽管 operator<<
的这种重载并不常见。
您的 operator<<
将其第一个参数作为 non-const 参考。您不能将 non-const 引用绑定到临时文件。但是 MyClass()
return 是临时的 newly-created 实例。
另外,func
return 是一个值,operator<<
return 是一个引用。那么它除了复制到 return 还能做什么?
Now I am returning this class via lvalue like this:
MyClass func()
{
return MyClass();
}
不,返回的表达式是一个 xvalue(一种右值),用于初始化 return-by-value 的结果(自从 C ++17,但这仍然是它的要点;此外,您使用的是 C++11)。
In this case the move constructor gets called when returning the class object and everything works as expected.
确实;一个右值将初始化一个右值引用,因此整个事情可以匹配移动构造函数。
When I change the code above:
… 现在表达式是 MyClass() << 5
,它的类型是 MyClass&
。这绝不是右值。这是一个左值。这是一个引用现有对象的表达式。
因此,如果没有明确的 std::move
,那将用于 copy-initialise 结果。而且,由于您的复制构造函数已被删除,因此无法正常工作。
我对这个示例编译完全感到惊讶,因为临时不能用于初始化左值引用(您的操作员的第一个参数),尽管已知一些工具链 (MSVS) 接受它作为扩展。
then would return std::move(MyClass() << 5);
work?
是的,我相信是这样。
然而,这看起来很奇怪,并使 reader double-check 确保没有悬挂引用。这表明有更好的方法可以实现此目的,从而使代码更清晰:
MyClass func()
{
MyClass m;
m << 5;
return m;
}
现在你仍然可以移动(因为 that's a special rule when return
ing local variables),没有任何奇怪的滑稽动作。而且,作为奖励,<<
调用完全是 standard-compliant.
假设我有 class MyClass
具有正确的移动构造函数并且其复制构造函数被删除。现在我像这样返回 class:
MyClass func()
{
return MyClass();
}
在这种情况下,移动构造函数在返回 class 对象时被调用,一切都按预期进行。
现在假设 MyClass
有一个 <<
运算符的实现:
MyClass& operator<<(MyClass& target, const int& source)
{
target.add(source);
return target;
}
当我更改上面的代码时:
MyClass func()
{
return MyClass() << 5;
}
我收到编译器错误,无法访问复制构造函数,因为它已被删除。但是为什么在这种情况下完全使用复制构造函数?
您的运营商 return MyClass&
。所以你 return 是左值,而不是可以自动移动的右值。
您可以依靠有关 NRVO 的标准保证来避免复制。
MyClass func()
{
MyClass m;
m << 5;
return m;
}
这将完全消除或移动对象。都是因为它是一个函数局部对象。
另一种选择,当您尝试在右值上调用 operator<<
时,是提供处理右值引用的重载。
MyClass&& operator<<(MyClass&& target, int i) {
target << i; // Reuse the operator you have, here target is an lvalue
return std::move(target);
}
这将使 MyClass() << 5
本身格式正确(请参阅其他答案以了解为什么不是),并且 return 可以构造 return 对象的 xvalue。尽管 operator<<
的这种重载并不常见。
您的 operator<<
将其第一个参数作为 non-const 参考。您不能将 non-const 引用绑定到临时文件。但是 MyClass()
return 是临时的 newly-created 实例。
另外,func
return 是一个值,operator<<
return 是一个引用。那么它除了复制到 return 还能做什么?
Now I am returning this class via lvalue like this:
MyClass func() { return MyClass(); }
不,返回的表达式是一个 xvalue(一种右值),用于初始化 return-by-value 的结果(自从 C ++17,但这仍然是它的要点;此外,您使用的是 C++11)。
In this case the move constructor gets called when returning the class object and everything works as expected.
确实;一个右值将初始化一个右值引用,因此整个事情可以匹配移动构造函数。
When I change the code above:
… 现在表达式是 MyClass() << 5
,它的类型是 MyClass&
。这绝不是右值。这是一个左值。这是一个引用现有对象的表达式。
因此,如果没有明确的 std::move
,那将用于 copy-initialise 结果。而且,由于您的复制构造函数已被删除,因此无法正常工作。
我对这个示例编译完全感到惊讶,因为临时不能用于初始化左值引用(您的操作员的第一个参数),尽管已知一些工具链 (MSVS) 接受它作为扩展。
then would return
std::move(MyClass() << 5);
work?
是的,我相信是这样。
然而,这看起来很奇怪,并使 reader double-check 确保没有悬挂引用。这表明有更好的方法可以实现此目的,从而使代码更清晰:
MyClass func()
{
MyClass m;
m << 5;
return m;
}
现在你仍然可以移动(因为 that's a special rule when return
ing local variables),没有任何奇怪的滑稽动作。而且,作为奖励,<<
调用完全是 standard-compliant.