什么时候应该使用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
是一个左值。 所以从foo
到meh
的“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一个右值或使用移动语义。
看这里。