127

我不完全理解标准::移动().也就是说,我对MSVC标准库中的这个实现感到困惑:

内联模板typename tr1::_Remove_reference<_Ty>::类型&&移动(Ty&&_Arg){//forward_Arg为可移动return((类型名tr1::_Remove_reference<_Ty>::_Type&&)_Arg);}

当我打电话时标准::移动这样地:

对象obj1;对象obj2=std::move(obj1);//_Ty&&_Arg绑定到obj1

... the_精氨酸引用参数绑定到左值对象1.你不能直接将右值引用绑定到左值,这让我认为对右值引用的强制转换(对象&&)是必需的。然而,这是荒谬的,因为标准::移动()必须适用于所有值。

为了完全理解这是如何工作的,我研究了标准::删除引用同样:

模板<类_Ty>结构_删除引用{//删除引用类型定义类型;};模板<类_Ty>结构删除引用{//删除引用类型定义类型;};模板<类_Ty>结构删除引用{//删除rvalue引用类型定义类型;};

不幸的是,它仍然令人困惑,我不明白。请帮助我了解标准::移动.

  • 34
    @NicolBolas相反,问题本身表明OP在这一声明中低估了自己。这正是OP的抓住基本的语法技能,使他们甚至可以提出问题。 评论 2015年4月16日1:17
  • 1
    无论如何,我都不同意——你可以很快地学习,或者已经很好地掌握了计算机科学或整个编程,即使是在C++中,你仍然很难掌握语法。最好是花时间学习力学、算法等,但根据需要依靠参考来复习语法,而不是在技术上表达清楚,但对复制/移动/前进之类的东西有一个肤浅的理解。在你知道move做什么之前,你应该如何知道“当你想移动构建东西时,何时以及如何使用std::move”?
    – 约翰·P
    评论 2019年3月13日10:54
  • 我想我来这里是为了了解移动工作原理而不是它是如何实现的。我觉得这个解释很有用:pagefault.blog/2018/03/01/…. 评论 2020年2月15日12:13

2个答案2

重置为默认值
197

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

模板<类型名T>typename remove_reference<T>::类型&&move(T&&arg){return static_cast<typename remove_reference<T>::type&&>(arg);}

让我们从更简单的部分开始,即当函数使用rvalue调用时:

对象a=std::move(Object());//Object()是临时的,它是prvalue

和我们的移动模板实例化如下:

//用[T=对象]移动:remove_reference<对象>::type&&move(对象&&arg){return static_cast<remove_reference<Object>:类型&>(arg);}

删除引用(_R)转换T型&T型T型&&T型、和对象不是参考,我们的最终功能是:

对象和移动(对象和参数){return static_cast<对象(arg);}

现在,你可能会想:我们甚至需要演员吗?答案是:是的,我们有。原因很简单;命名的rvalue引用被视为左值(标准禁止从左值到右值引用的隐式转换)。


下面是我们打电话时发生的情况移动使用左值:

对象a;//a是左值对象b=std::移动(a);

和相应的移动实例化:

//用[T=对象&]移动remove_reference<对象>::type&&move(对象&&&arg){return static_cast<remove_reference<Object>::type>(arg);}

再一次,删除引用转换对象&对象我们得到:

对象和移动(对象和参数){return static_cast<对象(arg);}

现在我们进入棘手的部分:什么是对象(&O)&&即使是卑鄙,它又如何与左值绑定?

为了实现完美转发,C++11标准为引用折叠提供了特殊规则,如下所示:

对象&&=对象&Object&&&=对象&Object&&&=对象&Object&&&&=对象&&

正如你所见,根据这些规则对象(&O)&&实际上是指对象&,它是允许绑定左值的纯左值引用。

因此,最终功能是:

对象和移动(对象和参数){return static_cast<对象(arg);}

这与之前使用rvalue的实例化没有什么不同,它们都将其参数转换为rvalue引用,然后返回它。不同的是,第一个实例化只能用于rvalue,而第二个实例化则用于lvalue。


解释为什么我们需要删除引用再多一点,让我们试试这个函数

模板<类型名T>T&&wanna_be_move(T&&arg){return static_cast<T&&>(arg);}

并用左值实例化它。

//wanna_be_move[with T=对象&]对象&&&wanna_be_move(对象&&&参数){return static_cast<对象&&>(arg);}

应用上面提到的引用折叠规则,您可以看到我们得到了一个无法使用的函数移动(简单地说,你用lvalue调用它,就会得到lvalue)。如果有的话,这个函数就是标识函数。

对象&wanna_be_move(对象&arg){return static_cast<对象->(arg);}
12
  • 4
    回答得很好。虽然我知道对于左值来说,评估是一个好主意T型作为对象&,我不知道这真的完成了。我早就料到了T型也要评估为对象在这种情况下,我认为这就是引入包装引用和标准::参考,或者不是。 评论 2011年9月22日17:08
  • 2
    两者之间有区别模板<typename T>void f(T arg)(这是维基百科文章中的内容)和模板<typename T>void f(T&arg)。第一个解析为值(如果要传递引用,则必须将其包装在标准::参考),而第二个始终解析为引用。遗憾的是,模板参数推导的规则相当复杂,所以我无法提供准确的理由T型&&解决方案对象(&O)&&(但这确实发生了)。
    – 维特斯
    评论 2011年9月22日17:58
  • 1
    但是,有任何理由认为这种直接的方法行不通吗?模板<typename T>T&&also_wanna_be_move(T&arg){return static_cast<T&&>(arg);}
    – 格雷戈
    评论 2014年1月9日16:45
  • 1
    这解释了为什么remove_reference是必要的,但我仍然不明白为什么函数必须使用t&&(而不是t&)。如果我正确理解了这个解释,那么不管你得到的是t&&还是t&,因为无论哪种方式,remove_reference都会将其强制转换为t,然后再加上&&。那么,为什么不说你接受T&(语义上就是你接受的),而不是T&&,并依靠完美的转发来允许呼叫者传递T&呢? 评论 2014年5月11日4:55
  • 4
    @mgiuca:如果你愿意标准::移动只将左值转换为右值,那么是的,T型&会没事的。这个技巧主要是为了灵活性:您可以调用标准::移动一切(包括rvalue)并取回rvalue。
    – 维特斯
    评论 2014年5月11日12:53
4

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

对象obj1;对象obj2=标准::移动(obj1);

_Ty是“Object&”类型

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

更像是

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

如果我们不删除引用,就会像我们所做的那样

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

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

它以这种方式减少的原因是支持完美转发。请参见这篇论文.

2
  • 这并不能解释为什么_删除引用(_R)_是必要的。例如,如果您有对象&作为typedef,如果对其进行引用,仍然会得到对象&。为什么这不适用于&&?那里答案是,这与完美转发有关。 评论 2011年9月22日6:21
  • 2
    没错。答案很有趣。A&&&被简化为A&,因此如果我们尝试使用(ObjectRef&&)obj1_ref,在本例中我们将获得Object&。 评论 2011年9月22日7:00

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览已标记的其他问题问你自己的问题.