在多个返回语句的情况下,使用`std::move`返回是否明智?

24

我知道通常不建议使用std::move返回值,即

bigObject foo() { bigObject result; /*...*/ return std::move(result); }

不是简单地

bigObject foo() { bigObject result; /*...*/ return result; }

因为它会妨碍返回值优化。但是对于有多个不同返回的函数,特别是像

class bar {
  bigObject fixed_ret;
  bool use_fixed_ret;
  void prepare_object(bigObject&);
 public:
  bigObject foo() {
    if(use_fixed_ret)
      return fixed_ret;
     else{
      bigObject result;
      prepare_object(result);
      return result;
    }
  }
};

我认为在这样一个函数中普通的返回值优化是不可能的,所以将其放入是否是个好主意呢?

      return std::move(result);

是应该写成这样,还是更好的写法是(我个人认为比较丑,但这是有争议的)

  bigObject foo() {
    bigObject result;
    if(use_fixed_ret)
      result = fixed_ret;
     else{
      prepare_object(result);
    }
    return result;
  }

我不能引用标准,所以我不会给出答案,但我确信你不需要使用std :: move,我认为你混淆了RVO和复制省略,复制省略是一种优化,在某些编译器上有一个单一的返回路径。无论返回在哪里,BigObject都将成为R值。 - 111111
是的,我认为复制省略就是我一开始想说的。 - leftaroundabout
1
如果你喜欢干净的代码和代码级别的优化,就像你似乎喜欢的那样,:), 那么你可以在foo()函数中摆脱else{...分支。因为如果第一个语句为真,则第二个语句将不会被评估。 - 111111
在你的例子中,你正在移动你的成员变量fixed_ret。因此多次调用该函数可能无法正常工作。 - balki
@balki,“移动您的成员变量'fixed_ret'”吗?那怎么做? - leftaroundabout
@leftaroundabout,@balki,return fixed_ret;会创建一个副本。 - scpayson
1个回答

34
对于局部变量,在大多数情况下,通常不需要在return语句中使用std::move将它们移动,因为语言实际上要求自动完成此操作:

§12.8 [class.copy] p32

当满足复制操作省略的标准或者只是源对象是函数参数时,并且要复制的对象由左值指定时,首先按照对象由右值指定的方式执行重载解析以选择复制的构造函数。如果重载解析失败,或所选构造函数的第一个参数类型不是对象类型的右值引用(可能带有cv限定符),则再次执行重载解析,将对象视为左值。 [注意:无论是否发生复制省略,都必须执行这两个阶段的重载解析。如果未执行省略,它确定要调用的构造函数,并且即使调用被省略,所选构造函数也必须可访问。- 结束说明]


† 复制省略的适用范围非常有限(§12.8/31)。其中之一的限制是当处理返回语句时,源对象的类型必须与函数的cv限定符去除后的返回类型相同。它还不适用于即将超出范围的局部变量的子对象。


2
这是您的标准参考。 - 111111
1
好的...我总是在标准的表述上遇到困难,现在已经读了五遍,但仍然不太明白它的意思。但你说,在我的例子中,它只是意味着“第一个版本就是最好的”?那么我会相信你的。 - leftaroundabout
2
@leftaroundabout:它基本上是说:“如果对象是本地的,并且通过值返回,请首先尝试将其作为rvalue返回(就像std::move一样)。如果找不到移动构造函数,请尝试相同的操作,但这次作为lvalue返回。” - Xeo
1
@Xeo 我可以简化一下吗,先尝试移动构造函数,如果不行再使用拷贝构造函数? - balki

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接