为什么`std :: move`命名为`std :: move`?

How*_*ant 120 c++ c++-faq rvalue-reference move-semantics c++11

C++ 11 std::move(x)函数根本不会移动任何东西.它只是一个转换为r值.为什么这样做?这不是误导吗?

How*_*ant 168

这是正确std::move(x)的只是对rvalue的强制转换 - 更具体地说是一个xvalue,而不是一个prvalue.拥有一个名为的演员move有时会让人感到困惑.但是,这种命名的目的不是混淆,而是让你的代码更具可读性.

move可追溯到2002年原始移动提案的历史.本文首先介绍了rvalue参考,然后展示了如何编写更高效的参考std::swap:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(static_cast<T&&>(a));
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(tmp);
}
Run Code Online (Sandbox Code Playgroud)

人们不得不回想起在历史的这一点上," &&"可能意味着唯一的东西是合乎逻辑的.没有人熟悉右值引用,也不熟悉将左值转换为右值的含义(虽然不像副本那样制作副本static_cast<T>(t)).所以这段代码的读者自然会想到:

我知道swap应该如何工作(复制到临时然后交换值),但那些丑陋的演员的目的是什么?!

另请注意,这swap实际上只是各种置换修改算法的替代品.这个讨论很多,比大得多swap.

然后该提议引入了语法糖,它取代了static_cast<T&&>更具可读性的东西,而不是精确的东西,而是原因:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}
Run Code Online (Sandbox Code Playgroud)

move只是语法糖static_cast<T&&>,现在代码非常暗示为什么那些演员阵容在那里:启用移动语义!

人们必须明白,在历史背景下,很少有人真正理解rvalues和移动语义之间的密切联系(尽管本文试图解释这一点):

给定rvalue参数时,移动语义将自动发挥作用.这是非常安全的,因为从rvalue移动资源不能被程序的其余部分注意到(没有其他人为了检测差异而引用了rvalue).

如果当时swap是这样呈现:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(cast_to_rvalue(a));
    a = cast_to_rvalue(b);
    b = cast_to_rvalue(tmp);
}
Run Code Online (Sandbox Code Playgroud)

然后人们就会看到并说:

但是你为什么要施展到右撇子?


要点:

事实上,使用move,没有人问过:

但你为什么要搬家?


随着岁月的流逝和提案的完善,左值和左值的概念被细化为我们今天的价值类别:

分类

(从dirkgently无耻地偷走的形象)

所以今天,如果我们想要swap准确地说它在做什么,而不是为什么,它应该看起来更像:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(set_value_category_to_xvalue(a));
    a = set_value_category_to_xvalue(b);
    b = set_value_category_to_xvalue(tmp);
}
Run Code Online (Sandbox Code Playgroud)

每个人都应该问自己的问题是上面的代码是否比以下代码更具可读性:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}
Run Code Online (Sandbox Code Playgroud)

甚至原来:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(static_cast<T&&>(a));
    a = static_cast<T&&>(b);
    b = static_cast<T&&>(tmp);
}
Run Code Online (Sandbox Code Playgroud)

在任何情况下,熟练的C++程序员都应该知道,在幕后move,没有什么比演员更多了.并且初学者C++程序员,至少有move,将被告知,意图是从rhs 移动,而不是从rhs 复制,即使他们不确切地知道如何完成.

此外,如果程序员希望以另一个名称std::move使用此功能,则不具备此功能的垄断权,并且其实现中不涉及非可移植语言魔法.例如,如果想要编码set_value_category_to_xvalue并使用它,那么这样做很简单:

template <class T>
inline
constexpr
typename std::remove_reference<T>::type&&
set_value_category_to_xvalue(T&& t) noexcept
{
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}
Run Code Online (Sandbox Code Playgroud)

在C++ 14中,它变得更加简洁:

template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
    return static_cast<std::remove_reference_t<T>&&>(t);
}
Run Code Online (Sandbox Code Playgroud)

因此,如果你是如此倾向,那么装饰你static_cast<T&&>自己认为最好,也许你最终会开发出一种新的最佳实践(C++不断发展).

那么move在生成的目标代码方面做了什么呢?

考虑一下test:

void
test(int& i, int& j)
{
    i = j;
}
Run Code Online (Sandbox Code Playgroud)

编译clang++ -std=c++14 test.cpp -O3 -S,生成此对象代码:

__Z4testRiS_:                           ## @_Z4testRiS_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movl    (%rsi), %eax
    movl    %eax, (%rdi)
    popq    %rbp
    retq
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

现在,如果测试更改为:

void
test(int& i, int& j)
{
    i = std::move(j);
}
Run Code Online (Sandbox Code Playgroud)

这里是绝对没有任何改变的目标代码.人们可以将这个结果概括为:对于可移动的物体,std::move没有任何影响.

现在让我们看一下这个例子:

struct X
{
    X& operator=(const X&);
};

void
test(X& i, X& j)
{
    i = j;
}
Run Code Online (Sandbox Code Playgroud)

这会产生:

__Z4testR1XS0_:                         ## @_Z4testR1XS0_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    popq    %rbp
    jmp __ZN1XaSERKS_           ## TAILCALL
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

如果你__ZN1XaSERKS_经历c++filt它产生: X::operator=(X const&).这里不足为奇.现在,如果测试更改为:

void
test(X& i, X& j)
{
    i = std::move(j);
}
Run Code Online (Sandbox Code Playgroud)

然后在生成的目标代码中仍然没有任何变化. std::move除了j强制转换为rvalue之外什么都没做,然后rvalue X绑定到副本赋值运算符X.

现在让我们添加一个移动赋值运算符X:

struct X
{
    X& operator=(const X&);
    X& operator=(X&&);
};
Run Code Online (Sandbox Code Playgroud)

现在目标代码确实改变了:

__Z4testR1XS0_:                         ## @_Z4testR1XS0_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    popq    %rbp
    jmp __ZN1XaSEOS_            ## TAILCALL
    .cfi_endproc
Run Code Online (Sandbox Code Playgroud)

运行__ZN1XaSEOS_通过c++filt揭示X::operator=(X&&)被称为代替X::operator=(X const&).

就是全部std::move!它在运行时完全消失.它唯一的影响是在编译时它可能改变调用什么重载.

  • 这是该图的一个点源:我重新创建了它`digraph D {glvalue - > {lvalue; xvalue} rvalue - > {xvalue; prvalue} expression - > {glvalue; rvalue}}`为了公共利益:)下载它**[这里作为SVG](http://downloads.sehe.nl/stackoverflow/value-categories.svg)** (7认同)
  • 这仍然适合自行车运动吗?我建议`allow_move`;) (7认同)
  • Scott Meyers建议将`std :: move`重命名为`rvalue_cast`:http://www.youtube.com/watch?v = BebbcQIuCsY&feature = player_detailpage#t = 335 (6认同)
  • 因为rvalue现在引用prvalues和xvalues,所以`rvalue_cast`的含义是不明确的:它返回什么样的rvalue?`xvalue_cast`在这里是一致的名字.不幸的是,大多数人在这个时候也不会理解它在做什么.再过几年,我的陈述有望变得虚假. (6认同)
  • @dyp或`please_move`或`mark_as_movable`. (3认同)
  • @dyp我的最爱仍然是"可移动的". (2认同)
  • `std :: enable_move`可能是一个更好的名字,或`std :: try_move`,或`std :: make_moveable`? (2认同)
  • @Andy:仅*两年后,我就更新了答案来解决您的问题。;-) (2认同)

pod*_*ova 19

让我在这里引用B. Stroustrup撰写的C++ 11 FAQ中的引用,这是对OP问题的直接回答:

move(x)表示"你可以将x视为右值".也许如果move()被称为rval()会更好,但到现在为止,move()已经使用多年了.

顺便说一句,我真的很喜欢常见问题 - 值得一读.

  • 从另一个答案中抄袭@HowardHinnant 的评论:Stroustrup 答案不准确,因为现在有两种右值 - 右值和 x 值,而 std::move 确实是一个 xvalue 转换。 (2认同)