27 April 2023

C++ 核心指南目录

“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 的动作。这个动作最好是赋值操作,该操作重置对象的值。