std :: move()如何将值传递给RValues?

时间:2011-09-22 06:00:20

标签: c++ c++11 move-semantics

我发现自己并没有完全理解std::move()的逻辑。

起初,我用谷歌搜索了它,但似乎只有关于如何使用std::move()的文档,而不是它的结构是如何工作的。

我的意思是,我知道模板成员函数是什么,但当我在VS2010中查看std::move()定义时,它仍然令人困惑。

std :: move()的定义如下。

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

首先对我来说奇怪的是参数,(_Ty&amp;&amp; _Arg),因为当我调用函数时,如下所示,

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

它基本上等于

// std::move()
_Ty&& _Arg = Obj1;

但正如您已经知道的那样,您无法直接将LValue链接到RValue引用,这让我觉得它应该是这样的。

_Ty&& _Arg = (Object&&)obj1;

然而,这是荒谬的,因为std :: move()必须适用于所有值。

所以我想完全理解这是如何工作的,我也应该看看这些结构。

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

不幸的是,它仍然令人困惑,我不明白。

我知道这都是因为我缺乏关于C ++的基本语法技巧。 我想知道这些工作如何彻底,我可以在互联网上获得的任何文件都会受到欢迎。 (如果你能解释一下,那也很棒)。

2 个答案:

答案 0 :(得分:140)

我们从移动功能开始(我稍微清理了一下):

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

让我们从更容易的部分开始 - 也就是说,当使用rvalue调用函数时:

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&转换为TT&&转换为T,而Object不是引用,我们的最终功能是:< / p>

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

与之前使用rvalue的实例化没有什么不同 - 它们都将其参数转换为rvalue引用然后返回它。不同之处在于,第一个实例化只能与rvalues一起使用,而第二个实例化则可以使用左值。


为了解释为什么我们需要更多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);
}

答案 1 :(得分:2)

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

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

_Ty是“Object&amp;”类型

这就是_Remove_reference是必要的原因。

更像是

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

如果我们没有删除引用,那就像我们正在做的那样

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

但是ObjectRef&amp;&amp;缩小为Object&amp;,我们无法绑定到obj2。

它减少这种方式的原因是支持完美转发。请参阅this paper