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

时间:2019-06-25 10:02:38

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

假设我的类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;
}

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

3 个答案:

答案 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 returning 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<<返回一个引用。那么除了复制后还能做些什么呢?