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個引用疊加規則和相應的模板參數類型推導都說完了,謝謝你們!

後記:

      在下愛鑽研,喜探究,實事求是;但另外一方面,又着實才疏學淺,能力有限,因此只能作一些基礎性的工做。但即使如此,也不免有疏漏乃至錯誤之處,這裏,在

  下懇請你們批評指正,不吝賜教。您的批評指正就是在下不斷進步的源泉!

相關文章
相關標籤/搜索