You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// move.h/** * @brief Convert a value to an rvalue. * @param __t A thing of arbitrary type. * @return The parameter cast to an rvalue-reference to allow moving it.*/template<typename _Tp>
constexprtypename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ returnstatic_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
于是我们也就更加清楚地了解了std::move()的功能:std::move()只是修改成右值引用。具体地说,T&改成T&&,T&&还是T&&;const T&改成const T&&, const T&&还是const T&&。其实源码上面的注释也说了:The parameter cast to an rvalue-reference to allow moving it. 应当注意,std::move()和"move"(包括移动构造、移动赋值)没有直接的关系。如果类并没有相应的移动函数,std::move()也做不了什么。毕竟它只是"to allow moving it"。
std::move()
的原理你真的了解
std::move()
吗?(我知道我不)我们都知道
std::move()
可以把左值转换为右值,然后就可以方便地使用move类的东西了。但是有时候std::move()
的行为好像比较奇怪。以第三次作业第二题(引用?复制?)为例:运行一下就可以知道,HERE处实际上调用的是拷贝构造函数。为什么不调用移动构造函数?
其实上一届的同学也讨论过这个问题,采用了实验的方法,参见关于std::move()到底干了什么? · Issue #32 · thu-coai/THUOOP · GitHub。这次我们做些理论层面的工作,来看一看
std::move()
究竟是怎么实现的。模板的特化
如果已经学习了这部分知识或不感兴趣可以跳过这一节。
首先简要介绍一下模板的特化(specialization)。顾名思义,特化就是模板针对某些特殊情形的特别处理,也就是对某些特别的类型参数的处理。为什么要特化?因为对于特定的情况,如果你能给出更合适的实现,那么当然就该用你提供的。
模板分为类模板和函数模板,而特化分为全特化和偏特化。全特化就是完全限定模板要用的参数,偏特化就是部分限定参数。例如:
第一个是基本的函数模板,有两个类型参数
T1
、T2
。第二个和第三个都是其特化。其中第二个是全特化,类型名Test
后面的<int, char>
限定T1
为int
,T2
为char
,所以template
后面的<>
里是空的(没有variable了)。第三个是偏特化,类型名Test
后只把T1
限定为int
,而T2
仍保留,所以template
后面的<>
里还有T2
。特化如何工作呢?看如下例子:
这是
Test
类的实例化(instantiation,不要与特化混淆)。其实就是个匹配的过程。编译器先看能不能用特化的版本,如果不能的话就还是用基础的版本。理应如此。此外,类模板可以全特化、偏特化,而函数模板只能全特化(其他的可以通过重载实现)。这已经不是本文需要的内容了,我们只需要对特化的工作方式有个感性认识就可以继续了。
std::move()
的实现如果使用vscode,按住ctrl单击
std::move()
即可跳转至其实现。上代码:……有点复杂?其实没有看上去那么复杂。可以拆出以下几个部分:
template<typename _Tp>
:这是模板函数的标志。_Tp
是类型参数。constexpr typename std::remove_reference<_Tp>::type&&
:这是返回值类型。constexpr
表示编译期常量,可以暂时忽略。typename
指示编译器,接下来的东西是个类型名。真正的返回值类型其实就是std::remove_reference<_Tp>::type
,也就是std::remove_reference<_Tp>
这个类里面定义的type
。move(_Tp&& __t)
:这是函数名及其参数。noexcept
表示不会抛出异常。__t
进行一个static_cast
,目标类型是刚才提到过的std::remove_reference<_Tp>::type&&
。由此可见,
std::move()
实现的关键在于std::remove_reference<_Tp>
。*若不感兴趣可以跳过此部分。*它的源码可以是这样的:
第一个是基本模板,而第二个和第三个则是它的特化。特化在这里便发挥了像“选择”一样的作用,决定了
std::remove_reference
的具体实现。如果是普通的_Tp
,那么就用第一个;如果是_Tp&
,即_Tp
的左值引用,就用第二个;同理,_Tp&&
就用第三个。然而,无论哪个版本,都用typedef
把_Tp
定义为type
。因此,如果传进普通的_Tp
,type
毫无疑问是_Tp
;如果传进来的是带引用(&
)的_Tp
,无论是左值引用还是右值引用,模板推导规则会把引用从_Tp
上“剥离”,从而type
还是_Tp
原本的样子。由此便达到了所谓remove_reference
的效果。因为这都是基于模板推导实现的,所以以上均在编译期即可完成。*跳到这里 ~* 回过头看
move
便一目了然。std::remove_reference
正如其名,只是去掉了引用标签,那么所谓的std::remove_reference<_Tp>::type&&
,其实就是两步:先把_Tp
上的引用标签都去掉,取出这个“纯粹”的类型,再强行打上一个右值引用的标签,就得到了目标类型。然后由static_cast
转换,一步到位。于是我们也就更加清楚地了解了
std::move()
的功能:std::move()
只是修改成右值引用。具体地说,T&
改成T&&
,T&&
还是T&&
;const T&
改成const T&&
,const T&&
还是const T&&
。其实源码上面的注释也说了:The parameter cast to an rvalue-reference to allow moving it. 应当注意,std::move()
和"move"(包括移动构造、移动赋值)没有直接的关系。如果类并没有相应的移动函数,std::move()
也做不了什么。毕竟它只是"to allow moving it"。至于本文开头的问题,也就很好解释了。
std::move()
接受一个const T&
,出来的就是const T&&
。如果构造函数的参数只接受const T&
(拷贝)和T&&
(移动)型,那么const T&&
就只能按万能的const T&
来调用拷贝构造,毕竟不能随随便便把const
的东西当成非const
处理。杂谈
一些可以进一步研究的东西:
一言不合就千百行报错,你值得拥有)参考文献
The text was updated successfully, but these errors were encountered: