为什么`std :: move`命名为`std :: move`? [英] Why is `std::move` named `std::move`?

查看:140
本文介绍了为什么`std :: move`命名为`std :: move`?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

code>只是一个转换为右值 - 更具体地说,是中偷走)



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

 模板< 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);
}

大家应该问自己的问题是,不太可读:

 模板< class T& 
void
swap(T& a,T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}

或甚至原来的:

 模板< class T> 
void
swap(T& a,T& b)
{
T tmp(static_cast<&&>(a));
a = static_cast< T&&>(b);
b = static_cast< T&&>(tmp);
}



在任何情况下,熟练的C ++程序员应该知道, code> 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&&>
}

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

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

所以如果你这么倾向,装饰你的 static_cast ;&&> 然而你认为最好,也许你会最终开发一个新的最佳实践(C ++不断发展)。



那么 move 在生成的对象代码方面有什么作用?



测试

  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& / code>。这里没有惊喜。现在如果测试更改为:

  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 !它在运行时完全消失。它唯一的影响是在编译时,它可以改变什么重载被调用。


The C++11 std::move(x) function doesn't really move anything at all. It is just a cast to r-value. Why was this done? Isn't this misleading?

解决方案

It is correct that std::move(x) is just a cast to rvalue - more specifically to an xvalue, as opposed to a prvalue. And it is also true that having a cast named move sometimes confuses people. However the intent of this naming is not to confuse, but rather to make your code more readable.

The history of move dates back to the original move proposal in 2002. This paper first introduces the rvalue reference, and then shows how to write a more efficient 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);
}

One has to recall that at this point in history, the only thing that "&&" could possibly mean was logical and. No one was familiar with rvalue references, nor of the implications of casting an lvalue to an rvalue (while not making a copy as static_cast<T>(t) would do). So readers of this code would naturally think:

I know how swap is supposed to work (copy to temporary and then exchange the values), but what is the purpose of those ugly casts?!

Note also that swap is really just a stand-in for all kinds of permutation-modifying algorithms. This discussion is much, much bigger than swap.

Then the proposal introduces syntax sugar which replaces the static_cast<T&&> with something more readable that conveys not the precise what, but rather the why:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}

I.e. move is just syntax sugar for static_cast<T&&>, and now the code is quite suggestive as to why those casts are there: to enable move semantics!

One must understand that in the context of history, few people at this point really understood the intimate connection between rvalues and move semantics (though the paper tries to explain that as well):

Move semantics will automatically come into play when given rvalue arguments. This is perfectly safe because moving resources from an rvalue can not be noticed by the rest of the program (nobody else has a reference to the rvalue in order to detect a difference).

If at the time swap was instead presented like this:

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);
}

Then people would have looked at that and said:

But why are you casting to rvalue?

As it was, using move, no one ever asked:

But why are you moving?

As the years went on and the proposal was refined, the notions of lvalue and rvalue were refined into the value categories we have today:

(image shamelessly stolen from dirkgently)

And so today, if we wanted swap to precisely say what it is doing, instead of why, it should look more like:

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);
}

And the question everyone should be asking themselves is if the above code is more or less readable than:

template <class T>
void
swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}

Or even the original:

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);
}

In any event, the journeyman C++ programmer should know that under the hood of move, nothing more is going on than a cast. And the beginner C++ programmer, at least with move, will be informed that the intent is to move from the rhs, as opposed to copy from the rhs, even if they don't understand exactly how that is accomplished.

Additionally, if a programmer desires this functionality under another name, std::move possesses no monopoly on this functionality, and there is no non-portable language magic involved in its implementation. For example if one wanted to code set_value_category_to_xvalue, and use that instead, it is trivial to do so:

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);
}

In C++14 it gets even more concise:

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

So if you are so inclined, decorate your static_cast<T&&> however you think best, and perhaps you will end up developing a new best practice (C++ is constantly evolving).

So what does move do in terms of generated object code?

Consider this test:

void
test(int& i, int& j)
{
    i = j;
}

Compiled with clang++ -std=c++14 test.cpp -O3 -S, this produces this object code:

__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

Now if the test is changed to:

void
test(int& i, int& j)
{
    i = std::move(j);
}

There is absolutely no change at all in the object code. One can generalize this result to: For trivially movable objects, std::move has no impact.

Now lets look at this examle:

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

void
test(X& i, X& j)
{
    i = j;
}

This generates:

__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

If you run __ZN1XaSERKS_ through c++filt it produces: X::operator=(X const&). No surprise here. Now if the test is changed to:

void
test(X& i, X& j)
{
    i = std::move(j);
}

Then there is still no change whatsoever in the generated object code. std::move has done nothing but cast j to an rvalue, and then that rvalue X binds to the copy assignment operator of X.

Now lets add a move assignment operator to X:

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

Now the object code does change:

__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

Running __ZN1XaSEOS_ through c++filt reveals that X::operator=(X&&) is being called instead of X::operator=(X const&).

And that's all there is to std::move! It completely disappears at run time. Its only impact is at compile-time where it might alter what overload gets called.

这篇关于为什么`std :: move`命名为`std :: move`?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆