【问题标题】:Why is the copy constructor called instead of the move constructor when returning?为什么返回时调用的是复制构造函数而不是移动构造函数?
【发布时间】:2019-11-07 03:22:41
【问题描述】:

假设我有一个类MyClass,它有一个正确的移动构造函数,并且它的复制构造函数被删除了。现在我像这样返回这个类:

MyClass func()
{
    return MyClass();
}

在这种情况下,移动构造函数在返回类对象时被调用,一切都按预期工作。

现在假设MyClass 实现了<< 运算符:

MyClass& operator<<(MyClass& target, const int& source)
{
    target.add(source);
    return target;
}

当我更改上面的代码时:

MyClass func()
{
    return MyClass() << 5;
}

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

【问题讨论】:

    标签: c++ c++11 return copy move


    【解决方案1】:

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

    MyClass func()
    {
        return MyClass();
    }
    

    不,返回的表达式是一个xvalue(一种右值),用于初始化按值返回的结果(从C++17开始事情有点复杂,但是这仍然是它的要点;此外,您使用的是 C++11)。

    在这种情况下,移动构造函数在返回类对象时被调用,一切都按预期工作。

    确实;一个右值将初始化一个右值引用,因此整个事情可以匹配移动构造函数。

    当我更改上面的代码时:

    ...现在表达式是MyClass() &lt;&lt; 5,其类型为MyClass&amp;。这绝不是右值。这是一个左值。这是一个引用现有对象的表达式。

    因此,如果没有明确的std::move,它将用于复制初始化结果。而且,由于您的复制构造函数已被删除,因此无法正常工作。


    我对示例编译完全感到惊讶,因为临时不能用于初始化左值引用(您的运算符的第一个参数),尽管已知某些工具链 (MSVS) 接受它作为扩展。


    那么会返回std::move(MyClass() &lt;&lt; 5); 工作吗?

    是的,我相信是的。

    然而,这看起来很奇怪,并且让读者仔细检查以确保没有悬空引用。这表明有一种更好的方法可以实现更清晰的代码:

    MyClass func()
    {
        MyClass m;
        m << 5;
        return m;
    }
    

    现在你仍然在行动(因为that's a special rule when returning local variables)没有任何奇怪的滑稽动作。另外,&lt;&lt; 调用完全符合标准。

    【讨论】:

    • 那么return std::move(MyClass() &lt;&lt; 5); 会起作用吗?
    • @RomCoo 我相信是的。然而,这看起来很奇怪,并且表明有更好的方法来实现这一点。
    • 表达式MyClass() &lt;&lt; 5 是否有效?我不这么认为! gccclang 正确拒绝此表达式。 mvc 接受它...您不能将右值绑定到 noconst 左值
    • @BiagioFesta 是的,我在回答中说过
    【解决方案2】:

    您的接线员返回MyClass&amp;。所以你返回的是一个左值,而不是一个可以自动移动的右值。

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

    MyClass func()
    {
        MyClass m;
        m << 5;
        return m;
    }
    

    这将完全忽略对象或移动它。都是因为它是一个函数本地对象。


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

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

    这将使MyClass() &lt;&lt; 5 本身具有良好的格式(请参阅其他答案以了解为什么不是),并返回一个可以构造返回对象的xvalue。虽然operator&lt;&lt; 的这种过载并不常见。

    【讨论】:

    • 大概他想返回operator&lt;&lt;的结果,而不是m
    • @DavidSchwartz - 也可以假定 operator&lt;&lt; 是惯用的,并以 return target; 结尾,签名会暗示这样。
    • 但至少对我而言,第一个参数上缺少const 表明并非如此。而且,这真的不是惯用的。如果i&lt;&lt;3 返回i,那就太奇怪了。
    • @DavidSchwartz - 无论哪种方式,这都是一个飞跃。我想我们都赶紧回答,而不是要求 OP 提供更好的例子。
    • @DavidSchwartz - 不是惯用语,什么?这就是流操作符所做的。
    【解决方案3】:

    您的operator&lt;&lt; 将其第一个参数作为非常量引用。您不能将非常量引用绑定到临时对象。但是MyClass() 将新创建的实例作为临时返回。

    另外,func 返回一个值,operator&lt;&lt; 返回一个引用。那么它除了复制返回还能做什么呢?

    【讨论】:

      猜你喜欢
      • 2015-06-10
      • 1970-01-01
      • 2013-04-25
      • 2012-04-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-21
      相关资源
      最近更新 更多