返回时为什么调用拷贝构造函数而不是move构造函数?

Rom*_*Coo 9 c++ copy return move c++11

假设我有一个MyClass带有正确的move构造函数的类,并且该类的副本构造函数被删除了。现在,我将像这样返回此类:

MyClass func()
{
    return MyClass();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,返回类对象时将调用move构造函数,并且一切都会按预期进行。

现在,假设MyClass有一个<<操作符的实现:

MyClass& operator<<(MyClass& target, const int& source)
{
    target.add(source);
    return target;
}
Run Code Online (Sandbox Code Playgroud)

当我更改上面的代码时:

MyClass func()
{
    return MyClass() << 5;
}
Run Code Online (Sandbox Code Playgroud)

我收到编译器错误,因为复制构造函数被删除,因此无法访问它。但是为什么在这种情况下完全使用了复制构造函数呢?

Lig*_*ica 15

现在,我通过左值返回此类,如下所示:

MyClass func()
{
    return MyClass();
}
Run Code Online (Sandbox Code Playgroud)

不,返回的表达式是一个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;
}
Run Code Online (Sandbox Code Playgroud)

现在您仍然可以采取行动(因为return输入局部变量时这是一条特殊的规则),并且没有任何奇怪的滑稽动作。而且,作为奖励,该<<电话完全符合标准。

  • @BiagioFesta是的,我在回答中说过 (2认同)

Sto*_*ica 8

您的操作员将按返回MyClass&。因此,您返回的是左值,而不是可以自动移动的右值。

您可以依靠有关NRVO的标准保证来避免复制。

MyClass func()
{
    MyClass m;
    m << 5;
    return m;
}
Run Code Online (Sandbox Code Playgroud)

这将使对象完全消失或移动。全部归因于它是一个函数局部对象。


当您尝试调用operator<<右值时,另一种选择是提供处理右值引用的重载。

MyClass&& operator<<(MyClass&& target, int i) {
    target << i; // Reuse the operator you have, here target is an lvalue
    return std::move(target);
}
Run Code Online (Sandbox Code Playgroud)

这将使MyClass() << 5自身结构良好(请参阅其他答案以了解为什么不是这样),并返回一个可以构造返回对象的xvalue。尽管这样和超载operator<<并不常见。