背景:
最近在学习C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函数,不想由此引起了一系列的“探索”,因而就有了如今这篇博文。html
前言:
右值引用无疑是C++11新特性中一颗耀眼的明珠,在此基础上实现了移动语义和完美转发,三者构成了令不少C++开发者拍案叫绝的“铁三角”(固然不是全部C++开发者)。而在这个“铁三角”中,有一个没法回避的关键细节,那就是引用叠加规则和模板参数类型推导规则。其实,关于这两个规则,可查到的资料很多,但都有一个特色——简单(就形式而言)而难懂(就理解而言)(起码在下这么认为),并且,都没有例证,仅仅是简明扼要地交代。而本文偏偏是将这一细节展开,给出演示和证实。诚然,这不是什么开创性的工做,但在下认为也是必不可少的,由于它让人们对这一关键细节了解得更加深刻和透彻,另外,从某个角度来讲,也填补了空白。函数
“图说”是由于:有图有真相,一目了然,真真切切,不容辩驳。
“VS2013下”是由于:本文全部测试和截图都来自VS2013,考虑到不一样编译环境下结果可能会略有不一样,因此,严谨起见,这里加了“VS2013下”。学习
最后,再说两点:
1.本文的行文形式(也能够说是逻辑顺序):先结论,再证实,必要时加以解说。测试
2.本文使用了大量的截图,因此读起来可能会有一种连篇累牍之感(但实际上文章逻辑结构清晰,内容一目了然),给读者带来的阅读上的不适,敬请谅解。spa
参考资料:
1.维基百科.右值引用 地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8(强烈建议你们看)
2.聚客频道.[C++] 右值引用:移动语义与完美转发 做者:Dutor 地址:http://ju.outofmemory.cn/entry/105978
3.博客园.【原】C++ 11完美转发 做者:Hujian 地址:http://www.cnblogs.com/hujian/archive/2012/02/17/2355207.html
4.IBM developerWorks.C++11 标准新特性: 右值引用与转移语义 做者:李胜利 地址:http://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/3d
正文:code
好了,书归正文。
为把问题说清楚,咱们先给出如下函数:orm
template<typename T> void f(T&& fpar)//formal parameter 形参 { //函数体 } //调用 int a=1; int& apar=a;//actual parameter 实参 f(apar);
在此基础上,给出如下表格(设A为基本类型,好比int):htm
表1对象
说明:
1.在前面的代码中,调用前形参fpar被声明的类型是T&&,调用时传入的实参apar的类型是int&。
2.上表中,二、三、4列对应了引用叠加规则,二、三、5列对应了模板参数类型推导规则。
3.由上表能够知道:
引用叠加规则的规律是:调用前fpar与apar中有一个是&,结果(即调用后fpar的实际类型)就是&;只有当fpar与apar都是&&时,结果才是&&。
模板参数类型推导规则的规律是:只有调用前fpar是&&,apar是&时,调用后T的实际类型才是A&,其他3种状况下都是A。(仅就上表,许多资料上不上这样,缘由
在于红色部分不同,见下面的说明4)
4.注意到上表中红色的A,在查阅过的资料中,那个位置是A&,但在下获得的结果倒是A,后面会详细解释。
5.本文所讨论的模板参数类型推导,仅是针对上面例子中的T而言的,在C++11里,更经典的类型推导包括auto,decltype等。
下面逐一给出验证与说明:
1.验证规则1
看图:
图1
程序中咱们设断点监视变量,咱们看到,ra做为int&类实参调用函数wai(由于是外层函数,这里简单命名为wai,不影响说明问题),调用后,T& w_a变成了int& w_a(即实际类型成了int&),而T w_aa成了int型,即T的类型是int型。这里,调用后形参w_a的实际类型知足引用叠加规则1(上表中的)。
关于引用叠加,有两种理解方式(以上例为例说明):
方式一:
参数传递时,T&与int&“做用”,结果是int&,即T&+int& -> int&。咱们将其视为规定,没必要解释。(上表正是以这种方式给出的)
方式二:
参数传递时,将实参ra前面的int&传给T(即将T换成int&),因而,int& & -> int&(注意int& &的两个‘&’间有空格,不是右值引用),而将int& & ->
int&视为规则。基于方式二,上表将变成(不考虑调用后T的类型):
表2
其中,第1个”加数“是将T换成的内容,也就是实参前的类型,第2个”加数“是函数参数列表中T后的引用形式,”和“是函数调用后形参的实际形式。下面图说方式二中规定的正确性:
A& & -> A& A& && -> A& A&& & -> A& A&& && -> A&&
两种方式均可以。只不过在下以为,方式二绕一点,而且,有一种T先变成int&(以图1所示为例),而后又变成int的莫名其妙之感。因此,在下推荐方式一。
在T的推导上,咱们采用这样的方式:先由叠加原理得出函数调用后形参的类型,而后将该类型与函数参数列表中形参的类型进行对比、匹配,从而得出T的类型。
若是发现不能匹配,则再次运用叠加规则”推导“出T的类型(咱们将在验证规则3时遇到这种状况)。
以图1中的状况为例:
T& w_a (形参列表中的)
int& w_a (函数调用后形参的实际类型,由叠加规则决定)
对比知,T为int型。
2.验证规则2
图说:
图2
这彷佛已经验证了规则2,但请看下图:
图3
不知是否有人会惊讶,a明明是右值引用,为何会调用void f(int& lfa)?换句话说,a何时变成了左值?
如今,要告诉你们一个结论(相信许多人都知道,就当在下是重复吧):
C++标准规定,具名的右值引用被看成左值。[注 6]这一规定的意义在于,右值引用原本是用于实现移动语义,于是须要绑定一个对象的内存地址,而后具备修改这一对象内容的权限,这些操做与左值绑定彻底同样。右值绑定与左值绑定的分野在于肯定函数重载时的分辨。对于移动构形成员函数与移动赋值运算符成员函数,其形、实参数结合时是按照右值引用处理;而在这两个成员函数体内部,因为形参都是具名的,于是都被看成左值,这就能够用该形参来修改传入对象的内部状态。另外,右值引用做为xvalue(临终值)原本是用于移动语义中一次性搬空其内容。具名使其具备更为持久的生存期,这是危险的,于是规定具名后为左值引用,除非程序显式指定其类型强制转换为右值引用。
——维基百科 地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8
另外,从上图也能够看出,&&和&的不一样能够做为重载标志。
如今,相信你们也再也不惊讶。回过头来看图2,咱们明白,这个验证是无效的,ra被当成左值,至关于仍是在验证规则1。那么,怎么办呢?看下图:
图4
虽然结论没有变化,但这种验证方法是有效的。
读者能够在图4代码的基础上,加入图3中的两个f函数,而后在main函数中写f(rt());会获得“右值:1”这样的输出。为缩短文章篇幅,这里就不截图了,请读者本身验证。
关于图4的代码,说如下几点:
1.前面说过,具名右值引用按左值引用处理,因此,要达到实验目的,不能将具名右值引用传给函数wai(),因此咱们传函数返回值这样的不具名右值引用。
2.若是咱们返回局部变量或是临时对象的引用(好比在rt()函数中写int a=1;return a++;,哪怕将int a=1;放在全局,也是不行的,由于a++就是返回++前a的一份拷贝,属于临时对象),结果是不正确的(得不到输出1)。(具体缘由在下暂时还不清楚,多是后边的代码执行时将临时变量的空间覆盖(重写)了,在下反汇编单步也没找出确切的答案(在下汇编学得不怎么样),这里烦请有知道缘由的大牛给出指点,在下感激涕零,先行谢过)
3.就像你们在图4中看到的那样,rt()函数中必须将全局变量a强制类型转换为int&&型再返回,不然,若是写成return a;,编译器将产生相似“没法将右值引用绑定到左值”的报错,缘由是具名右值引用a被当作左值。
4.void wai(const T& w_a)中的const不能省,缘由是很是量引用(T&)不能接受右值引用。
5.void nei(const int& n_a)中的const也不能省,正如你们在图4中看到的,在wai()中执行nei(w_a);时,w_a为const int&类型。
简单说一下T的推导:
const T& w_a (参数列表中)
const int& w_a (函数调用后w_a的实际类型)
对比知,T为int型。
至此,咱们能够肯定,表1中红色的A是正确的,A&的说法有误。
3.验证规则3
图说:
这里只说一下T的推导。以下:
T&& w_a (参数列表中w_a的类型)
int& w_a (函数调用后w_a的实际类型)
显然,此时没法直接匹配。这里咱们运用表2(之因此用表2,是由于表2比表1更加直观)中的第2条A& + && -> A&,推出T为int&类型。
4.验证规则4
图说:
这里首先说一点,前边咱们说过,很是量左值引用不能接受右值引用,上图中,void nei(int& n_a),w_a为int&&类型,那么,rt()中的nei(w_a);是如何经过的呢?
不要忘了,虽然w_a显示为int&&类型,但它是具名右值引用,因此做为左值引用处理,天然可以经过。若是咱们将void nei(int& n_a)改成void nei(int&& n_a),反而不能经过(w_a被当作int&型,int&&不能接受int&),读者能够本身试一试。
再说一下T的推导:
T&& w_a (参数列表中w_a的类型)
int&& w_a (函数调用后w_a的实际类型,不考虑C++11将其视为int&)
对比,知T为int型。
至此,4个引用叠加规则和相应的模板参数类型推导都说完了,谢谢你们!
后记:
在下爱钻研,喜探究,实事求是;但另外一方面,又着实才疏学浅,能力有限,因此只能作一些基础性的工做。但即使如此,也不免有疏漏乃至错误之处,这里,在
下恳请你们批评指正,不吝赐教。您的批评指正就是在下不断进步的源泉!