何时应该在函数返回值上使用std :: move?

use*_*453 109 c++ move-semantics c++11

在这种情况下

struct Foo {};
Foo meh() {
  return std::move(Foo());
}
Run Code Online (Sandbox Code Playgroud)

我很确定移动是不必要的,因为新创建的Foo将是一个xvalue.

但在这种情况下呢?

struct Foo {};
Foo meh() {
  Foo foo;
  //do something, but knowing that foo can safely be disposed of
  //but does the compiler necessarily know it?
  //we may have references/pointers to foo. how could the compiler know?
  return std::move(foo); //so here the move is needed, right?
}
Run Code Online (Sandbox Code Playgroud)

我认为需要采取行动吗?

Ste*_*sop 114

在的情况下return std::move(foo);move是多余的,因为12.8/32:

当满足或将满足复制操作的省略标准时,除了源对象是函数参数这一事实,并且要复制的对象由左值指定,重载决策选择复制的构造函数是首先执行,好像对象是由右值指定的.

return foo;是NRVO的案例,因此允许复制省略.foo是一个左值.所以从选择"复制"构造foo到的返回值meh必须是移动构造函数(如果存在).

但是,添加move会产生潜在的影响:它可以防止移动被忽略,因为return std::move(foo);符合NRVO的条件.

据我所知,12.8/32列出了左移的副本可以被移动替换的唯一条件.一般情况下,编译器不允许在复制后检测左值未使用(比如使用DFA),并自行进行更改.我在这里假设两者之间存在可观察到的差异 - 如果可观察行为相同则应用"假设"规则.

因此,要回答标题中的问题,请在std::move需要移动时使用返回值,并且无论如何都不会移动.那是:

  • 你希望它被移动,并且
  • 它是一个左值,而且
  • 它不符合复制条款的条件,并且
  • 它不是按值函数参数的名称.

考虑到这是非常繁琐而且移动通常很便宜,您可能会说在非模板代码中您可以简化这一点.使用std::move时间:

  • 你希望它被移动,并且
  • 它是一个左值,而且
  • 你不能担心它.

通过遵循简化的规则,你牺牲了一些移动省略.对于std::vector那些移动便宜的类型,你可能永远不会注意到(如果你注意到你可以优化).对于std::array移动成本昂贵的类型,或者你不知道移动是否便宜的模板,你更有可能担心它.

  • C++:骨头简单.显然. (43认同)
  • C++:如果你不笑,你会哭. (30认同)
  • 当函数声明为返回 `std::unique_ptr<Base>` 时,尝试返回声明为 `std::unique_ptr<Derived>` 的变量怎么样?在 gcc 和 mingw-w64 中它可以正常工作,但是 vanilla mingw(基于 gcc 4.9.3,目标是 `i686-pc-cygwin`)需要 `std::move(x)` 来编译。 (2认同)

And*_*owl 33

在这两种情况下都不需要这样做.在第二种情况下,std::move因为你按值返回一个局部变量是多余的,编译器会理解,因为你不再使用那个局部变量,所以它可以被移动而不是被复制.

  • 更正:使用std :: move*作为返回值*被认为有害,可以防止elision (20认同)
  • 使用`std :: move`认为有害,可以防止省略 (6认同)

Dav*_*eas 18

在返回值上,如果返回表达式直接引用本地左值的名称(即此时为xvalue),则不需要std::move.另一方面,如果返回表达式不是标识符,则不会自动移动它,例如,std::move在这种情况下,您需要显式:

T foo(bool which) {
   T a = ..., b = ...;
   return std::move(which? a : b);
   // alternatively: return which? std::move(a), std::move(b);
}
Run Code Online (Sandbox Code Playgroud)

直接返回命名局部变量或临时表达式时,应避免显式std::move.在这些情况下,编译器必须(并且将来)自动移动,并且添加std::move可能会影响其他优化.


Yak*_*ont 14

关于什么时候不应该移动,有很多答案,但问题是"什么时候应该移动?"

这是一个何时应该使用的人为例子:

std::vector<int> append(std::vector<int>&& v, int x) {
  v.push_back(x);
  return std::move(v);
}
Run Code Online (Sandbox Code Playgroud)

也就是说,当你有一个带右值引用的函数时,修改它,然后返回它的副本.现在,在实践中,这种设计几乎总是更好:

std::vector<int> append(std::vector<int> v, int x) {
  v.push_back(x);
  return v;
}
Run Code Online (Sandbox Code Playgroud)

这也允许您采用非右值参数.

基本上,如果您想要通过移动返回的函数中有右值引用,则必须调用std::move.如果你有一个局部变量(无论是否为参数),则隐式返回moves(并且可以省略这种隐式移动,而不能显式移动).如果您有一个接受局部变量的函数或操作,并返回对所述局部变量的引用,则必须进行std::move移动(例如,三元?:运算符).

  • 在`int`上使用`std :: move`没有任何好处; 如果`x`是一个带有昂贵副本的类类型(例如附加到字符串),这可能是一个更好的例子 (2认同)