溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

發(fā)布時(shí)間:2020-06-26 15:11:43 來(lái)源:網(wǎng)絡(luò) 閱讀:231 作者:劉元興 欄目:網(wǎng)絡(luò)安全

 

 

背景:
    最近在學(xué)習(xí)C++STL,出于偶然,在C++Reference上看到了vector下的emplace_back函數(shù),不想由此引發(fā)了一系列的“探索”,于是就有了現(xiàn)在這篇博文。

前言:
      右值引用無(wú)疑是C++11新特性中一顆耀眼的明珠,在此基礎(chǔ)上實(shí)現(xiàn)了移動(dòng)語(yǔ)義和完美轉(zhuǎn)發(fā),三者構(gòu)成了令很多C++開(kāi)發(fā)者拍案叫絕的“鐵三角”(當(dāng)然不是所有C++開(kāi)發(fā)者)。而在這個(gè)“鐵三角”中,有一個(gè)無(wú)法回避的關(guān)鍵細(xì)節(jié),那就是引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則。其實(shí),關(guān)于這兩個(gè)規(guī)則,可查到的資料不少,但都有一個(gè)特點(diǎn)——簡(jiǎn)單(就形式而言)而難懂(就理解而言)(起碼在下這么認(rèn)為),而且,都沒(méi)有例證,僅僅是簡(jiǎn)明扼要地交代。而本文恰恰是將這一細(xì)節(jié)展開(kāi),給出演示和證明。誠(chéng)然,這不是什么開(kāi)創(chuàng)性的工作,但在下認(rèn)為也是必不可少的,因?yàn)樗屓藗儗?duì)這一關(guān)鍵細(xì)節(jié)了解得更加深入和透徹,另外,從某個(gè)角度來(lái)說(shuō),也填補(bǔ)了空白。

“圖說(shuō)”是因?yàn)椋?/span>有圖有真相,一目了然,真真切切,不容辯駁。
“VS2013下”是因?yàn)椋?/span>本文所有測(cè)試和截圖都來(lái)自VS2013,考慮到不同編譯環(huán)境下結(jié)果可能會(huì)略有不同,所以,嚴(yán)謹(jǐn)起見(jiàn),這里加了“VS2013下”。

最后,再說(shuō)兩點(diǎn):
    1.本文的行文形式(也可以說(shuō)是邏輯順序):先結(jié)論,再證明,必要時(shí)加以解說(shuō)。

    2.本文使用了大量的截圖,所以讀起來(lái)可能會(huì)有一種連篇累牘之感(但實(shí)際上文章邏輯結(jié)構(gòu)清晰,內(nèi)容一目了然),給讀者帶來(lái)的閱讀上的不適,敬請(qǐng)諒解。

參考資料:
1.維基百科.右值引用   地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8(強(qiáng)烈建議大家看)
2.聚客頻道.[C++] 右值引用:移動(dòng)語(yǔ)義與完美轉(zhuǎn)發(fā)   作者:Dutor   地址:http://ju.outofmemory.cn/entry/105978
3.博客園.【原】C++ 11完美轉(zhuǎn)發(fā)   作者:Hujian   地址:http://www.cnblogs.com/hujian/archive/2012/02/17/2355207.html
4.IBM developerWorks.C++11 標(biāo)準(zhǔn)新特性: 右值引用與轉(zhuǎn)移語(yǔ)義  作者:李勝利   地址:http://www.ibm.com/developerworks/cn/aix/library/1307_lisl_c11/

正文:

    好了,書(shū)歸正文。
 為把問(wèn)題說(shuō)清楚,我們先給出以下函數(shù):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

template<typename T>void f(T&& fpar)//formal parameter 形參{    //函數(shù)體}//調(diào)用int a=1;int& apar=a;//actual parameter 實(shí)參f(apar);

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

 在此基礎(chǔ)上,給出以下表格(設(shè)A為基本類型,比如int):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則    表1

說(shuō)明:
     1.在前面的代碼中,調(diào)用前形參fpar被聲明的類型是T&&,調(diào)用時(shí)傳入的實(shí)參apar的類型是int&。
     2.上表中,2、3、4列對(duì)應(yīng)了引用疊加規(guī)則,2、3、5列對(duì)應(yīng)了模板參數(shù)類型推導(dǎo)規(guī)則。
     3.由上表可以知道:
       引用疊加規(guī)則的規(guī)律是:調(diào)用前fpar與apar中有一個(gè)是&,結(jié)果(即調(diào)用后fpar的實(shí)際類型)就是&;只有當(dāng)fpar與apar都是&&時(shí),結(jié)果才是&&。
       模板參數(shù)類型推導(dǎo)規(guī)則的規(guī)律是:只有調(diào)用前fpar是&&,apar是&時(shí),調(diào)用后T的實(shí)際類型才是A&,其余3種情況下都是A。(僅就上表,許多資料上不上這樣,
原因

                                     在于紅色部分不一樣,見(jiàn)下面的說(shuō)明4)
     4.注意到上表中紅色的A,在查閱過(guò)的資料中,那個(gè)位置是A&,但在下得到的結(jié)果卻是A,后面會(huì)詳細(xì)解釋。
     5.本文所討論的模板參數(shù)類型推導(dǎo),僅是針對(duì)上面例子中的T而言的,在C++11里,更經(jīng)典的類型推導(dǎo)包括auto,decltype等。

    下面逐一給出驗(yàn)證與說(shuō)明:

1.驗(yàn)證規(guī)則1

看圖:

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則         圖1

    程序中我們?cè)O(shè)斷點(diǎn)監(jiān)視變量,我們看到,ra作為int&類實(shí)參調(diào)用函數(shù)wai(因?yàn)槭峭鈱雍瘮?shù),這里簡(jiǎn)單命名為wai,不影響說(shuō)明問(wèn)題),調(diào)用后,T& w_a變成了int& w_a(即實(shí)際類型成了int&),而T w_aa成了int型,即T的類型是int型。這里,調(diào)用后形參w_a的實(shí)際類型滿足引用疊加規(guī)則1(上表中的)。

    關(guān)于引用疊加,有兩種理解方式(以上例為例說(shuō)明):

方式一:

          參數(shù)傳遞時(shí),T&與int&“作用”,結(jié)果是int&,即T&+int& -> int&。我們將其視為規(guī)定,不必解釋。(上表正是以這種方式給出的)

方式二:

        參數(shù)傳遞時(shí),將實(shí)參ra前面的int&傳給T(即將T換成int&),于是,int& & -> int&(注意int& &的兩個(gè)‘&’間有空格,不是右值引用),而將int& & ->

    int&視為規(guī)則?;诜绞蕉媳韺⒆兂桑ú豢紤]調(diào)用后T的類型):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則   表2

其中,第1個(gè)”加數(shù)“是將T換成的內(nèi)容,也就是實(shí)參前的類型,第2個(gè)”加數(shù)“是函數(shù)參數(shù)列表中T后的引用形式,”和“是函數(shù)調(diào)用后形參的實(shí)際形式。下面圖說(shuō)方式二中規(guī)定的正確性:

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則            C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則            C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則             C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

        A& & -> A&                                A& && -> A&                             A&& & -> A&                             A&& && -> A&&

      兩種方式都可以。只不過(guò)在下覺(jué)得,方式二繞一點(diǎn),并且,有一種T先變成int&(以圖1所示為例),然后又變成int的莫名其妙之感。所以,在下推薦方式一。

    在T的推導(dǎo)上,我們采用這樣的方式:先由疊加原理得出函數(shù)調(diào)用后形參的類型,然后將該類型與函數(shù)參數(shù)列表中形參的類型進(jìn)行對(duì)比、匹配,從而得出T的類型。

如果發(fā)現(xiàn)不能匹配,則再次運(yùn)用疊加規(guī)則”推導(dǎo)“出T的類型(我們將在驗(yàn)證規(guī)則3時(shí)遇到這種情況)。

以圖1中的情況為例:

                                                        T& w_a     (形參列表中的)

                                                      int& w_a     (函數(shù)調(diào)用后形參的實(shí)際類型,由疊加規(guī)則決定)

    對(duì)比知,T為int型。

2.驗(yàn)證規(guī)則2

圖說(shuō):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則        圖2

這似乎已經(jīng)驗(yàn)證了規(guī)則2,但請(qǐng)看下圖:

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則        圖3

    不知是否有人會(huì)驚訝,a明明是右值引用,為什么會(huì)調(diào)用void f(int& lfa)?換句話說(shuō),a什么時(shí)候變成了左值?

    現(xiàn)在,要告訴大家一個(gè)結(jié)論(相信許多人都知道,就當(dāng)在下是重復(fù)吧):

     C++標(biāo)準(zhǔn)規(guī)定,具名的右值引用被當(dāng)作左值。[注 6]這一規(guī)定的意義在于,右值引用本來(lái)是用于實(shí)現(xiàn)移動(dòng)語(yǔ)義,因而需要綁定一個(gè)對(duì)象的內(nèi)存地址,然后具有修改這一對(duì)象內(nèi)容的權(quán)限,這些操作與左值綁定完全一樣。右值綁定與左值綁定的分野在于確定函數(shù)重載時(shí)的分辨。對(duì)于移動(dòng)構(gòu)造成員函數(shù)與移動(dòng)賦值運(yùn)算符成員函數(shù),其形、實(shí)參數(shù)結(jié)合時(shí)是按照右值引用處理;而在這兩個(gè)成員函數(shù)體內(nèi)部,由于形參都是具名的,因而都被當(dāng)作左值,這就可以用該形參來(lái)修改傳入對(duì)象的內(nèi)部狀態(tài)。另外,右值引用作為xvalue(臨終值)本來(lái)是用于移動(dòng)語(yǔ)義中一次性搬空其內(nèi)容。具名使其具有更為持久的生存期,這是危險(xiǎn)的,因而規(guī)定具名后為左值引用,除非程序顯式指定其類型強(qiáng)制轉(zhuǎn)換為右值引用。
                                                             ——維基百科   地址:http://zh.wikipedia.org/wiki/%E5%8F%B3%E5%80%BC%E5%BC%95%E7%94%A8

     另外,從上圖也可以看出,&&和&的不同可以作為重載標(biāo)志。

     現(xiàn)在,相信大家也不再驚訝?;剡^(guò)頭來(lái)看圖2,我們明白,這個(gè)驗(yàn)證是無(wú)效的,ra被當(dāng)成左值,相當(dāng)于還是在驗(yàn)證規(guī)則1。那么,怎么辦呢?看下圖

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則        圖4

    雖然結(jié)論沒(méi)有變化,但這種驗(yàn)證方法是有效的。

    讀者可以在圖4代碼的基礎(chǔ)上,加入圖3中的兩個(gè)f函數(shù),然后在main函數(shù)中寫(xiě)f(rt());會(huì)得到“右值:1”這樣的輸出。為縮短文章篇幅,這里就不截圖了,請(qǐng)讀者自己驗(yàn)證。

關(guān)于圖4的代碼,說(shuō)以下幾點(diǎn):

1.前面說(shuō)過(guò),具名右值引用按左值引用處理,所以,要達(dá)到實(shí)驗(yàn)?zāi)康?,不能將具名右值引用傳給函數(shù)wai(),所以我們傳函數(shù)返回值這樣的不具名右值引用。

2.如果我們返回局部變量或是臨時(shí)對(duì)象的引用(比如在rt()函數(shù)中寫(xiě)int a=1;return a++;,哪怕將int a=1;放在全局,也是不行的,因?yàn)閍++就是返回++前a的一份拷貝,屬于臨時(shí)對(duì)象),結(jié)果是不正確的(得不到輸出1)。(具體原因在下暫時(shí)還不清楚,可能是后邊的代碼執(zhí)行時(shí)將臨時(shí)變量的空間覆蓋(重寫(xiě))了,在下反匯編單步也沒(méi)找出確切的答案(在下匯編學(xué)得不怎么樣),這里煩請(qǐng)有知道原因的大牛給出指點(diǎn),在下感激不盡,先行謝過(guò))

3.就像大家在圖4中看到的那樣,rt()函數(shù)中必須將全局變量a強(qiáng)制類型轉(zhuǎn)換為int&&型再返回,否則,如果寫(xiě)成return a;,編譯器將產(chǎn)生類似“無(wú)法將右值引用綁定到左值”的報(bào)錯(cuò),原因是具名右值引用a被當(dāng)做左值。

4.void wai(const T& w_a)中的const不能省,原因是非常量引用(T&)不能接受右值引用。

5.void nei(const int& n_a)中的const也不能省,正如大家在圖4中看到的,在wai()中執(zhí)行nei(w_a);時(shí),w_a為const int&類型。

簡(jiǎn)單說(shuō)一下T的推導(dǎo):

                                                          const T& w_a   (參數(shù)列表中)

                                                        const int& w_a   (函數(shù)調(diào)用后w_a的實(shí)際類型)

     對(duì)比知,T為int型。

     至此,我們可以確定,表1中紅色的A是正確的,A&的說(shuō)法有誤。

3.驗(yàn)證規(guī)則3

圖說(shuō):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

這里只說(shuō)一下T的推導(dǎo)。如下:

                                                     T&& w_a      (參數(shù)列表中w_a的類型)

                                                    int& w_a      (函數(shù)調(diào)用后w_a的實(shí)際類型)

顯然,此時(shí)無(wú)法直接匹配。這里我們運(yùn)用表2(之所以用表2,是因?yàn)楸?比表1更加直觀)中的第2條A& + && -> A&,推出T為int&類型。

4.驗(yàn)證規(guī)則4

圖說(shuō):

C++11 圖說(shuō)VS2013下的引用疊加規(guī)則和模板參數(shù)類型推導(dǎo)規(guī)則

    這里首先說(shuō)一點(diǎn),前邊我們說(shuō)過(guò),非常量左值引用不能接受右值引用,上圖中,void nei(int& n_a),w_a為int&&類型,那么,rt()中的nei(w_a);是如何通過(guò)的呢?

不要忘了,雖然w_a顯示為int&&類型,但它是具名右值引用,所以作為左值引用處理,自然能夠通過(guò)。如果我們將void nei(int& n_a)改為void nei(int&& n_a),反而不能通過(guò)(w_a被當(dāng)做int&型,int&&不能接受int&),讀者可以自己試一試。

    再說(shuō)一下T的推導(dǎo):

                                                          T&& w_a      (參數(shù)列表中w_a的類型)

                                                        int&& w_a      (函數(shù)調(diào)用后w_a的實(shí)際類型,不考慮C++11將其視為int&)

對(duì)比,知T為int型。

至此,4個(gè)引用疊加規(guī)則和相應(yīng)的模板參數(shù)類型推導(dǎo)都說(shuō)完了,謝謝大家!

后記:

      在下愛(ài)鉆研,喜探究,實(shí)事求是;但另一方面,又著實(shí)才疏學(xué)淺,能力有限,所以只能做一些基礎(chǔ)性的工作。但即便如此,也難免有疏漏乃至錯(cuò)誤之處,這里,在

  下懇請(qǐng)大家批評(píng)指正,不吝賜教。您的批評(píng)指正就是在下不斷進(jìn)步的源泉!


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI