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

2021-12-12 00:00:00 c++ c++11 move-semantics

在这种情况下

struct Foo {};
Foo meh() {
  return std::move(Foo());
}

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

I'm pretty sure that the move is unnecessary, because the newly created Foo will be an xvalue.

但是在这种情况下怎么办?

But what in cases like these?

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?
}

我想需要搬家吗?

推荐答案

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

In the case of return std::move(foo); the move is superfluous because of 12.8/32:

当满足或可能满足删除复制操作的条件时满足除了源对象是函数参数的事实,并且要复制的对象由左值指定,重载为副本选择构造函数的解析首先执行为如果对象由右值指定.

When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is ?rst performed as if the object were designated by an rvalue.

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

return foo; is a case of NRVO, so copy elision is permitted. foo is an lvalue. So the constructor selected for the "copy" from foo to the return value of meh is required to be the move constructor if one exists.

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

Adding move does have a potential effect, though: it prevents the move being elided, because return std::move(foo); is not eligible for NRVO.

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

As far as I know, 12.8/32 lays out the only conditions under which a copy from an lvalue can be replaced by a move. The compiler is not permitted in general to detect that an lvalue is unused after the copy (using DFA, say), and make the change on its own initiative. I'm assuming here that there's an observable difference between the two -- if the observable behavior is the same then the "as-if" rule applies.

因此,要回答标题中的问题,请在返回值上使用 std::move 当您希望它被移动并且无论如何都不会被移动时.即:

So, to answer the question in the title, use std::move on a return value when you want it to be moved and it would not get moved anyway. That is:

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

考虑到这非常繁琐,而且移动通常很便宜,您可能想说,在非模板代码中,您可以稍微简化一下.在以下情况下使用 std::move:

Considering that this is quite fiddly and moves are usually cheap, you might like to say that in non-template code you can simplify this a bit. Use std::move when:

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

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

By following the simplified rules you sacrifice some move elision. For types like std::vector that are cheap to move you'll probably never notice (and if you do notice you can optimize). For types like std::array that are expensive to move, or for templates where you have no idea whether moves are cheap or not, you're more likely to be bothered worrying about it.

相关文章