std :: move()如何将值转换为RValues?


104

我只是发现自己不完全了解的逻辑std::move()

起初,我在Google上进行了搜索,但似乎只有有关如何使用的文档std::move(),而不是其结构的工作原理。

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

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 && _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 ++的基本语法技能。我想知道这些工作原理是如何工作的,我在互联网上可以获得的任何文件都将受到欢迎。(如果您只能解释这一点,那也将很棒)。


31
@NicolBolas相反,问题本身表明,OP在该声明中向其推销自己。正是OP 对基本语法技能的掌握,使他们甚至可以提出问题。
凯尔·斯特兰德

无论如何,我都不同意-您可以快速学习或已经掌握了整个计算机科学或整个程序,甚至使用C ++也可以,但是仍然难以使用语法。最好花一些时间来学习技巧,算法等,但要根据需要依靠引用来完善语法,这要比从技术上明确表达但对复制/移动/转发等内容有较浅的理解。在知道什么动作之前,您应该如何知道“何时以及如何使用std :: move构造对象”?
约翰·P

我想我来这里是为了了解move工作原理,而不是如何实现它。我发现此解释非常有用:pagefault.blog/2018/03/01/…
安东·达涅科

Answers:


171

我们从移动功能开始(我整理了一点):

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_reference转换T&TT&&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_reference转换Object&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一起使用,而第二个实例化则与lvalues一起使用。


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

3
好答案。尽管我知道对于左值来说,将Tas 评估为一个好主意Object&,但我不知道这确实完成了。在这种情况下,我本来也希望对此T进行评估Object,因为我认为这是引入包装器引用和的原因std::ref,或者不是。
Christian Rau

2
template <typename T> void f(T arg)(Wikipedia文章中有此内容)和之间存在区别template <typename T> void f(T& arg)。第一个解析为值(如果要传递引用,则必须将其包装在中std::ref),而第二个则始终解析为引用。可悲的是,模板参数推导规则是相当复杂的,所以我不能提供精确的推理为什么T&&resovles到Object& &&(但它确实发生)。
Vitus

1
但是,是否有任何理由认为这种直接方法行不通?template <typename T> T&& also_wanna_be_move(T& arg) { return static_cast<T&&>(arg); }
greggo 2014年

1
这就解释了为什么remove_reference是必需的,但是我仍然不明白为什么函数必须采用T &&(而不是T&)。如果我正确理解了这个解释,则无论得到T &&还是T&都没关系,因为无论哪种方式,remove_reference都会将其强制转换为T,然后再添加&&。那么,为什么不说您接受T&(而不是T &&,而是依靠完美的转发来允许呼叫者通过T&)呢?
2014年

3
@mgiuca:如果只想std::move将左值转换为右值,那么T&可以。这个技巧主要是为了提高灵活性:您可以调用std::move所有内容(包括右值)并返回右值。
维特斯(Vitus)

4

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

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

_Ty是“对象&”类型

这就是为什么需要_Remove_reference的原因。

它会更像

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

如果我们不删除引用,那就像我们在做

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

但是ObjectRef &&简化为Object&,我们无法将其绑定到obj2。

它减少这种方式的原因是为了支持完美的转发。参见本文


这并不能解释为什么_Remove_reference_必须这样做的一切。例如,如果您拥有Object&typedef并引用了它,那么您仍然会得到Object&。为什么&&不起作用?这里一个问题的答案,它具有完美的转发去做。
Nicol Bolas

2
真正。答案非常有趣。A&&&简化为A&,因此,如果尝试使用(ObjectRef &&)obj1_ref,则在这种情况下将获得Object&。
Vaughn Cato
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.