假设我的类MyClass
具有正确的move构造函数,并且其复制构造函数已删除。现在,我将像这样返回此类:
MyClass func()
{
return MyClass();
}
在这种情况下,返回类对象时将调用move构造函数,并且一切都会按预期进行。
现在假设MyClass
具有<<
运算符的实现:
MyClass& operator<<(MyClass& target, const int& source)
{
target.add(source);
return target;
}
当我更改上面的代码时:
MyClass func()
{
return MyClass() << 5;
}
我收到编译器错误,因为复制构造函数被删除,因此无法访问它。但是在这种情况下为什么要使用复制构造函数呢?
答案 0 :(得分:15)
现在,我通过像这样的左值返回此类:
MyClass func() { return MyClass(); }
否,返回的表达式是 xvalue (一种rvalue),用于初始化按值返回的结果(自C ++ 17起,事情有些复杂,但是这仍然是要点;此外,您使用的是C ++ 11)。
在这种情况下,返回类对象时将调用move构造函数,并且一切都会按预期进行。
的确;一个右值将初始化一个右值引用,因此整个对象可以与move构造函数匹配。
当我更改上面的代码时:
…现在表达式为MyClass() << 5
,其类型为MyClass&
。这绝不是一个右值。这是一个左值。这是一个引用现有对象的表达式。
因此,在没有显式std::move
的情况下,该代码将用于复制初始化结果。而且,由于您的副本构造函数已删除,因此无法正常工作。
尽管所有示例工具链(MSVS)都接受此作为扩展,但由于示例不能完全初始化一个临时值来初始化左值引用(您的运算符的第一个参数),我对此感到很惊讶。
然后返回
std::move(MyClass() << 5);
的工作?
是的,我相信。
然而,这看起来很奇怪,并使读者仔细检查以确保没有悬挂的引用。这表明有一种更好的方法可以完成此操作,从而使代码更清晰:
MyClass func()
{
MyClass m;
m << 5;
return m;
}
现在,您仍然可以采取行动(因为that's a special rule when return
ing local variables)而没有任何奇怪的滑稽动作。另外,<<
调用完全符合标准。
答案 1 :(得分:8)
您的运算符返回MyClass&
。因此,您返回的是左值,而不是可以自动移动的右值。
您可以依靠有关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
本身结构良好(请参阅其他答案以了解为什么不是),并返回一个xvalue,可以从该xvalue构造返回对象。尽管operator<<
的这种情况和过载并不常见。
答案 2 :(得分:1)
您的operator<<
将其第一个参数用作非常量引用。您不能将非常量引用绑定到临时表。但是MyClass()
会将新创建的实例作为临时实例返回。
此外,尽管func
返回一个值,operator<<
返回一个引用。那么除了复制后还能做些什么呢?