C++11 图说VS2013下的引用叠加规则和模板参数类型推导规则

 

 

背景:
    最近在学习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个引用叠加规则和相应的模板参数类型推导都说完了,谢谢你们!

后记:

      在下爱钻研,喜探究,实事求是;但另外一方面,又着实才疏学浅,能力有限,因此只能作一些基础性的工做。但即使如此,也不免有疏漏乃至错误之处,这里,在

  下恳请你们批评指正,不吝赐教。您的批评指正就是在下不断进步的源泉!