CppCoreGuidelines ES.56 只有需要显式的把对象移动到另一个作用域范围时才用 std::move()
“Write std::move()
only when you need to explicitly move an object to
another scope”
理由
相比 copy ,移动 move能避免重复,提升性能。
移动完之后,留下的是一个空对象。这样就可能导致意外甚至危险情况。所以我们应该避免移动左值,因为左值可能后面会被访问。
注意
对于右值来说,移动是隐式的操作(比如函数的返回值)。所以,不要显式地写移动操作,导致代码复杂。编译器会自动进行优化。
通常来说,遵循本文档中的指南编写代码,可以避免大部分显式使用std::move
的情况:
- 避免变量的作用域范围不必要的大
- 编写篇幅短的函数
- 返回本地变量
显式移动操作用于把一个对象移动到另一个作用域范围。主要是传递数据到“地漏”函数,实现移动构造和移动赋值以及 swap 操作。
坏例子
void sink(X&& x); // sink takes ownership of x void user() { X x; // error: cannot bind an lvalue to a rvalue reference sink(x); // OK: sink takes the contents of x, x must now be assumed to be // empty sink(std::move(x)); // ... // probably a mistake use(x); }
通常,std::move()
用于传递&&
参数。并且在传递参数之后,对象就已经移出去,不要再读取该变量,直到赋予新值。
void f() { string s1 = "supercalifragilisticexpialidocious"; string s2 = s1; // ok, takes a copy assert(s1 == "supercalifragilisticexpialidocious"); // ok // bad, if you want to keep using s1's value string s3 = move(s1); // bad, assert will likely fail, s1 likely changed assert(s1 == "supercalifragilisticexpialidocious"); } int main() { f(); return EXIT_SUCCESS; }
Assertion failed: s1 == "supercalifragilisticexpialidocious"
例子
void sink(unique_ptr<widget> p); // pass ownership of p to sink() void f() { auto w = make_unique<widget>(); // ... sink(std::move(w)); // ok, give to sink() // ... sink(w); // Error: unique_ptr is carefully designed so that you // cannot copy it }
注意
std::move()
只是假装把变量类型转换成&&
。其本身不移动任何东西,而只是把一个对象标记成被移动的实体。编程语言本身知道哪些对象是可以移动的,尤其是函数的返回值,所以不需要添加多余的std::move()
。
不要仅仅因为你听说 move
效率更高而写std::move()
。没有数据支撑,不要声明效率高。一般来说,没有什么理由的话,不要把代码搞的太复杂。绝对不要对 const
对象使用std::move()
,其实只是悄悄的转成了一个副本。
坏例子
vector<int> make_vector() { vector<int> result; // ... load result with data return std::move(result); // bad; just write "return result;" }
不要移动本地变量。因为语言默认本地变量是可移动。以上写法没有帮助,只会让编译器干预 RVO(返回值优化)过程,产生本地变量的一个不必要的引用别名。
坏例子
vector<int> v = std::move(make_vector()); // bad; the std::move is // entirely redundant
不要移动返回值,比如在x = move(f())
中。编程语言知道返回值是一个临时对象,可以移动。
例子
void mover(X&& x) { call_something(std::move(x)); // ok call_something(std::forward<X>(x)); // bad, don't std::forward // an rvalue reference call_something(x); // suspicious, why not std::move? } template<class T> void forwarder(T&& t) { call_something(std::move(t)); // bad, don't std::move a // forwarding reference call_something(std::forward<T>(t)); // ok call_something(t); // suspicious, why not std::forward? }
强化
- 标记警告以下情况:
std::move(x)
,其中x
是右值,或这开发语言以右值对待的变量,包括从std::move(local_variable)
返回的值- 对于值返回的函数的返回结果进行移动操作
std::move(f())
- 标记警告接受
S&&
参数的函数,且该函数没有能接受左值const S&
的重载 - 标记警告以
std::move
的方式传参给函数的情况。除了以下情况:- 参数类型为
X&&
右值引用 - 类型只能移动,且参数是值传递的
- 参数类型为
- 标记警告
std::move
用于转发引用的情况(T&&
中T
为一个模板类型参数)。考虑使用std::forward
- 标记警告除了从右值引用到非
const
传参中使用std::move
的其他情况 - 标记警告
std::forward
用于右值引用的情况(X&&
中X
不是模板类型参数)。考虑使用std::move
- 标记警告除了转发引用之外的使用
std::forward
的情况 - 标记警告一个对象是可能从别处移动来的,而下一个操作是
const
操作。应该首先有一个非const
的动作。这个动作最好是赋值操作,该操作重置对象的值。