提问人:Howard Hinnant 提问时间:1/26/2014 最后编辑:Jan SchultkeHoward Hinnant 更新时间:9/10/2023 访问量:14576
为什么 std::move 被命名为 std::move?
Why is std::move named std::move?
答:
正确的是,这只是对 rvalue 的强制转换 - 更具体地说是 xvalue,而不是 prvalue。而且,有名的演员有时会让人们感到困惑,这也是事实。但是,此命名的目的不是混淆,而是使代码更具可读性。std::move(x)
move
其历史可以追溯到 2002 年的原始搬迁提案。本文首先介绍了右值引用,然后展示了如何编写一个更高效的:move
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);
}
人们不得不回想一下,在历史的这个时刻,“”唯一可能意味着的是合乎逻辑的。没有人熟悉右值引用,也不熟悉将左值转换为右值的含义(同时不会像那样复制)。因此,这段代码的读者会自然而然地认为:&&
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);
}
J.F. 只是 的语法糖,现在代码非常有启发性地说明为什么这些强制转换在那里:启用移动语义!move
static_cast<T&&>
人们必须明白,在历史的语境中,在这一点上,很少有人真正理解重值和移动语义之间的密切联系(尽管本文也试图解释这一点):
当给定 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);
}
然后人们会看着它说:
但是你为什么要铸造价值呢?
要点:
就这样,使用 ,从来没有人问过:move
但你为什么要搬家?
随着岁月的流逝和提案的完善,lvalue 和 rvalue 的概念被细化为我们今天拥有的价值类别:
(图片无耻地从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);
}
每个人都应该问自己的问题是,上面的代码是否比以下代码或多或少具有可读性:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
甚至是原版:
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);
}
无论如何,熟练的 C++ 程序员应该知道,在引擎盖下,除了演员之外,没有什么比这更重要的事情了。初学者 C++ 程序员,至少会被告知,目的是从 rhs 移动,而不是从 rhs 复制,即使他们不确切了解如何完成。move
move
此外,如果程序员希望以另一个名称使用此功能,则对此功能没有垄断权,并且其实现中不涉及不可移植的语言魔法。例如,如果有人想编码 ,并改用它,那么这样做是微不足道的: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);
}
在 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);
}
因此,如果您愿意,请按照您认为最好的方式进行装饰,也许您最终会开发出新的最佳实践(C++ 在不断发展)。static_cast<T&&>
那么,move
在生成的目标代码方面有什么作用呢?
考虑一下:test
void
test(int& i, int& j)
{
i = j;
}
使用 编译时,这将生成以下目标代码: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
现在,如果将测试更改为:
void
test(int& i, int& j)
{
i = std::move(j);
}
目标代码中绝对没有任何变化。可以将此结果概括为:对于微不足道的可移动对象,没有影响。std::move
现在让我们看一下这个例子:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
这将生成:
__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
如果运行它,则生成:。这并不奇怪。现在,如果将测试更改为:__ZN1XaSERKS_
c++filt
X::operator=(X const&)
void
test(X& i, X& j)
{
i = std::move(j);
}
然后,生成的对象代码仍然没有任何变化。 除了转换为右值之外什么也没做,然后该右值绑定到 的复制赋值运算符。std::move
j
X
X
现在让我们添加一个移动赋值运算符:X
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
现在,目标代码确实发生了变化:
__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
运行显示正在调用而不是 .__ZN1XaSEOS_
c++filt
X::operator=(X&&)
X::operator=(X const&)
仅此而已!它在运行时完全消失。它的唯一影响是在编译时,它可能会改变调用的重载。std::move
评论
digraph D { glvalue -> { lvalue; xvalue } rvalue -> { xvalue; prvalue } expression -> { glvalue; rvalue } }
allow_move
;)
movable
rvalue_cast
xvalue_cast
让我在这里引用 B. Stroustrup 撰写的 C++11 FAQ 中的一段话,这是对 OP 问题的直接回答:
move(x) 表示“您可以将 x 视为 rValue”。也许它会 如果 move() 被称为 rval() 会更好,但现在 move() 已经 已经使用了多年。
顺便说一句,我真的很喜欢常见问题解答 - 值得一读。
评论
下一个:什么是透明比较器?
评论
std::move
std::char_traits::move
std::remove()
erase()
move
remove
mark_movable()
move
mark_movable()