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

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 returning local variables),没有任何奇怪的滑稽动作。而且,作为奖励,<< 调用完全是 standard-compliant.