c++ std::move如何将表达式转换为右值?

mrfwxfqh  于 2023-10-21  发布在  其他
关注(0)|答案(2)|浏览(116)

我不完全理解std::move()的实现。也就是说,我对MSVC标准库中的这种实现感到困惑:

template<class _Ty> inline
typename tr1::_Remove_reference<_Ty>::_Type&&
move(_Ty&& _Arg)
{ // forward _Arg as movable
    return ((typename tr1::_Remove_reference<_Ty>::_Type&&)_Arg);
}

当我像这样调用std::move时:

Object obj1;
Object obj2 = std::move(obj1); // _Ty&& _Arg binds to obj1

. _Arg引用参数绑定到左值obj1。你不能直接将一个右值引用绑定到一个左值,这让我认为像(Object&&)这样的右值引用的强制转换是必需的。然而,这是荒谬的,因为std::move()必须对所有值都有效。
为了充分理解这是如何工作的,我也看了std::remove_reference的实现:

template<class _Ty>
struct _Remove_reference
{ // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&>
{ // remove reference
    typedef _Ty _Type;
};

template<class _Ty>
struct _Remove_reference<_Ty&&>
{ // remove rvalue reference
    typedef _Ty _Type;
};

不幸的是,它仍然是令人困惑的,我不明白。请帮助我理解std::move的实现。

cgh8pdjw

cgh8pdjw1#

我们从move函数开始(我稍微清理了一下):

template <typename T>
typename remove_reference<T>::type&& move(T&& arg)
{
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

让我们从更简单的部分开始-也就是说,当函数被右值调用时:

Object a = std::move(Object());
// Object() is temporary, which is prvalue

我们的move模板被示例化如下:

// move with [T = Object]:
remove_reference<Object>::type&& move(Object&& arg)
{
  return static_cast<remove_reference<Object>::type&&>(arg);
}

由于remove_referenceT&转换为T或将T&&转换为T,而Object不是引用,因此我们的最终函数是:

Object&& move(Object&& arg)
{
  return static_cast<Object&&>(arg);
}

现在,你可能会想:我们还需要演员表吗答案是:有的.原因很简单;命名的右值引用 * 被 * 视为左值(并且标准禁止从左值到右值引用的隐式转换)。
下面是当我们用左值调用move时发生的情况:

Object a; // a is lvalue
Object b = std::move(a);

以及相应的move示例化:

// move with [T = Object&]
remove_reference<Object&>::type&& move(Object& && arg)
{
  return static_cast<remove_reference<Object&>::type&&>(arg);
}

同样,remove_referenceObject&转换为Object,我们得到:

Object&& move(Object& && arg)
{
  return static_cast<Object&&>(arg);
}

现在我们进入棘手的部分:Object& &&是什么意思,它如何绑定到左值?
为了允许完美的转发,C++11标准提供了引用折叠的特殊规则,如下所示:

Object &  &  = Object &
Object &  && = Object &
Object && &  = Object &
Object && && = Object &&

正如你所看到的,在这些规则下,Object& &&实际上意味着Object&,这是一个允许绑定左值的普通左值引用。
最终函数是:

Object&& move(Object& arg)
{
  return static_cast<Object&&>(arg);
}

这与前面使用右值的示例化没有什么不同-它们都将其参数转换为右值引用,然后返回它。区别在于第一个示例化只能用于右值,而第二个示例化可以用于左值。
为了解释为什么我们需要remove_reference多一点,让我们试试这个函数

template <typename T>
T&& wanna_be_move(T&& arg)
{
  return static_cast<T&&>(arg);
}

并使用左值示例化它。

// wanna_be_move [with T = Object&]
Object& && wanna_be_move(Object& && arg)
{
  return static_cast<Object& &&>(arg);
}

应用上面提到的引用折叠规则,你可以看到我们得到了一个不能作为move使用的函数(简单地说,你用左值调用它,你会得到左值)。如果有的话,这个函数就是恒等函数。

Object& wanna_be_move(Object& arg)
{
  return static_cast<Object&>(arg);
}
7nbnzgx9

7nbnzgx92#

_Ty是模板参数,在这种情况下,

Object obj1;
Object obj2 = std::move(obj1);

_Ty是“Object &”类型
这就是为什么_Remove_reference是必要的。
它将很像

typedef Object& ObjectRef;
Object obj1;
ObjectRef&& obj1_ref = obj1;
Object&& obj2 = (Object&&)obj1_ref;

如果我们不删除引用,

Object&& obj2 = (ObjectRef&&)obj1_ref;

但是ObjectRef&&简化为Object &,我们无法将其绑定到obj2。
它以这种方式减少的原因是支持完美转发。看这张纸。

相关问题