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

在这种情况下

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

我很确定这个举动是不必要的,因为新创build的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? } 

我想这个举动是必要的吗?

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

当满足或将满足复制操作的条件时,除了源对象是函数参数,要复制的对象由左值指定的情况下,select复制的构造函数的重载parsing是首先执行,就好像对象是由右值指定的一样。

return foo; 是NRVO的情况,所以允许复制elision。 foo是一个左值。 所以从foomeh的“copy”select的构造函数必须是移动构造函数(如果存在的话)。

添加move确实有一个潜在的影响,但是:它阻止移动被删除,因为return std::move(foo); 符合NRVO的资格。

据我所知,12.8 / 32列出了一个可以用一个移动代替左翼副本的唯一条件。 一般情况下,编译器不允许在复制之后检测到左值(使用DFA),并自行进行更改。 我在这里假设两者之间存在明显的差异 – 如果可观察到的行为是相同的,那么“如果”规则适用。

所以,为了回答标题中的问题,当你想移动它的时候使用std::move作为返回值,并且它不会被移动。 那是:

  • 你希望它被移动,并且
  • 这是一个左翼,和
  • 这是不符合复制elision和
  • 它不是一个按值函数参数的名称。

考虑到这是相当繁琐的,移动通常很便宜,你可能会说,在非模板代码中,你可以简化这一点。 在以下情况下使用std::move

  • 你希望它被移动,并且
  • 这是一个左翼,和
  • 你不能担心这件事。

通过遵循简化的规则,你牺牲了一些移动elision。 对于像std::vector这样的移动便宜的types,你可能永远不会注意到(如果你注意到,你可以优化)。 对于像std::array这样昂贵的types的移动,或者对于你不知道移动是否便宜的模板,你更可能会担心这一点。

这两种情况都是不必要的。 在第二种情况下, std::move是多余的,因为你正在通过值返回一个局部variables,编译器会明白,因为你不会再使用这个局部variables,它可以被移动而不是被复制。

在返回值上,如果返回expression式直接指向本地左值的名称(即此时是一个xvalue),则不需要std::move 。 另一方面,如果返回expression式不是标识符,它将不会自动移动,所以例如,在这种情况下,您需要显式std::move

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

直接返回一个命名的局部variables或临时expression式时,应该避免显式的std::move 。 编译器必须 (也将在未来)在这些情况下自动移动,添加std::move可能会影响其他优化。

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

下面是一个应该使用的例子:

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

即当你有一个函数需要右值引用,修改它,然后返回它的一个副本。 现在,在实践中,这种devise几乎总是更好:

 std::vector<int> append(std::vector<int> v, int x) { v.push_back(x); return v; } 

这也允许你取非右值参数。

基本上,如果在函数中有一个右移引用,你必须调用std::move 。 如果你有一个局部variables(不pipe它是否是一个参数),返回隐式move s(这个隐式移动可以被消除,而一个明确的移动不能)。 如果你有一个接受局部variables的函数或者操作,并且返回一个对所述局部variables的引用,你必须std::move来移动才能发生(比如trinary?:操作符)。

一个C ++编译器可以自由使用std::move(foo)

  • 如果知道foo是在其生命的尽头,并且
  • std::move的隐式使用不会影响C ++规范所允许的语义效果以外的C ++代码的语义。

它依赖于C ++编译器的优化function,无论它是否能够计算f(foo); foo.~Foo();哪些转换f(foo); foo.~Foo(); f(foo); foo.~Foo();f(std::move(foo)); foo.~Foo(); f(std::move(foo)); foo.~Foo(); 在性能或内存消耗方面是有利可图的,同时遵循C ++规范的规则。


从概念上讲,2017年的C ++编译器(如GCC 6.3.0) 能够优化此代码:

 Foo meh() { Foo foo(args); foo.method(xyz); bar(); return foo; } 

进入这个代码:

 void meh(Foo *retval) { new (retval) Foo(arg); retval->method(xyz); bar(); } 

避免调用Foo的复制构造函数和析构函数。


2017年的C ++编译器(如GCC 6.3.0) 无法优化这些代码:

 Foo meh_value() { Foo foo(args); Foo retval(foo); return retval; } Foo meh_pointer() { Foo *foo = get_foo(); Foo retval(*foo); delete foo; return retval; } 

进入这些代码:

 Foo meh_value() { Foo foo(args); Foo retval(std::move(foo)); return retval; } Foo meh_pointer() { Foo *foo = get_foo(); Foo retval(std::move(*foo)); delete foo; return retval; } 

这意味着2017年的程序员需要明确地指定这样的优化。

当从一个函数返回时, std::move是完全不必要的,并且真正进入你的领域 – 程序员 – 试图保留你应该留给编译器的东西。

std::move从一个不是本地函数variables的函数中std::move某些东西时会发生什么? 你可以说你永远不会写这样的代码,但是如果你编写的代码很好,那么会发生什么,然后重构它,心不在焉,不要改变std::move 。 你会有乐趣跟踪这个错误。

另一方面,编译器大多不能做出这样的错误。

另外:重要的是要注意,从函数返回一个局部variables不一定会创build一个右值或使用移动语义。

看这里。