這個(gè)系列的文章來(lái)自于Effective Modern C++的讀書(shū)筆記,我抽取了其中比較重要的,不容易理解的,平常我們開(kāi)發(fā)過(guò)程中也不太在意的一些Item進(jìn)行分析。
? 在C++11中最為顯著的一個(gè)新特性,當(dāng)屬完美轉(zhuǎn)發(fā)了,是的它很完美,和它的名字一樣,轉(zhuǎn)發(fā)兩字意味著一個(gè)函數(shù)將其參數(shù)傳遞給另外一個(gè)函數(shù),第二個(gè)函數(shù)的目標(biāo)則是接收來(lái)自于第一個(gè)函數(shù)傳遞過(guò)來(lái)的對(duì)象,轉(zhuǎn)發(fā)二字體現(xiàn)出第二個(gè)函數(shù)接收到的對(duì)象應(yīng)該和第一個(gè)參數(shù)傳遞過(guò)來(lái)的是相同的,因此如果采用值傳遞的方式就沒(méi)有辦法達(dá)到效果了,因?yàn)榭截惡蟮膶?duì)象是原來(lái)的對(duì)象是兩個(gè)不同的對(duì)象,如果想在第二個(gè)函數(shù)中操作傳遞過(guò)來(lái)的對(duì)象也達(dá)不到效果,因?yàn)椴僮鞯氖菑?fù)制后的對(duì)象,并不是原對(duì)象。如果采用指針傳遞的話,的確是可以達(dá)到轉(zhuǎn)發(fā)的效果。但是這要求用戶必須傳遞指針,因此算不上完美。
? 完美轉(zhuǎn)發(fā)意味著不僅僅要轉(zhuǎn)發(fā)對(duì)象本身,還要轉(zhuǎn)發(fā)它附帶的屬性,它的類(lèi)型,左值或者是右值,是否是const
或volatile
,根據(jù)Item24中的介紹,這里只有通用引用可以做到。下面是一個(gè)完美轉(zhuǎn)發(fā)的例子。
不僅僅可以轉(zhuǎn)發(fā)一個(gè)參數(shù),還可以結(jié)合可變參數(shù)轉(zhuǎn)發(fā)多個(gè)參數(shù),如下:
template<typename T>void fwd(Ts&&... params) { f(std::forward<Ts>(params)...);}這個(gè)和STL中的make_shared和make_unique是一樣的,make_shared源碼如下:
template<typename _Tp, typename... _Args> inline shared_ptr<_Tp> make_shared(_Args&&... __args){ typedef typename std::remove_const<_Tp>::type _Tp_nc; return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(), std::forward<_Args>(__args)...);}? 然而完美轉(zhuǎn)發(fā)并不完美,在一些場(chǎng)合下會(huì)因?yàn)轭?lèi)型推導(dǎo)錯(cuò)誤導(dǎo)致轉(zhuǎn)發(fā)了錯(cuò)誤的類(lèi)型,最后導(dǎo)致執(zhí)行失敗,本文則是通過(guò)講解這些失敗的場(chǎng)景,讓我們知道哪些場(chǎng)景下完美轉(zhuǎn)發(fā)無(wú)法正常工作。
? 因?yàn)?code>std::vector<int>帶有列表初始化的構(gòu)造函數(shù),所以{1, 2, 3}
可以隱式構(gòu)造成std::vector<int>
。
? 但是如果將{1, 2, 3}通過(guò)完美轉(zhuǎn)發(fā)給f的話則導(dǎo)致編譯失敗。
fwd({1, 2, 3});? 上面的調(diào)用編譯失敗了,其原因是因?yàn)?code>{1, 2, 3}進(jìn)行模版推導(dǎo)失敗了,所以找不到匹配的fwd函數(shù),編譯出錯(cuò)的信息如下:
note: candidate template ignored: couldn't infer template argument 'T'? 這就是完美轉(zhuǎn)發(fā)遇到的第一個(gè)失敗的例子,解決這個(gè)問(wèn)題的方法也很簡(jiǎn)單,在Item2中介紹過(guò),盡管模版推導(dǎo)無(wú)法推導(dǎo)出初始化列表的正確類(lèi)型,但是auto可以,所以上面的代碼改成如下的形式就可以順利運(yùn)行。
auto il = {1, 2, 3};fwd(il);? 在Item8中提到過(guò),當(dāng)0或者是NULL作為指針類(lèi)型傳遞給一個(gè)模版函數(shù)的時(shí)候,會(huì)推導(dǎo)出錯(cuò)誤的類(lèi)型,把0或者NULL作為int
類(lèi)型,很顯然0或者NULL無(wú)法被當(dāng)作指針類(lèi)型進(jìn)行完美轉(zhuǎn)發(fā),解決這個(gè)問(wèn)題也很簡(jiǎn)單,使用C++11中的nullptr替換即可。
? 上面的MinVals只是聲明了,但是還沒(méi)有在類(lèi)外進(jìn)行定義,任何一個(gè)學(xué)過(guò)C++的人都知道靜態(tài)成員變量需要在類(lèi)外進(jìn)行初始化,但是static const
是一個(gè)例外,它可以不用在類(lèi)外進(jìn)行定義,但是它在內(nèi)存中不會(huì)分配實(shí)際的存儲(chǔ)。所以下面的代碼運(yùn)行一切正常。
? 編譯期就會(huì)直接把Widget::MinVals
替換成28了,然后進(jìn)行實(shí)際的調(diào)用了。一旦對(duì)Widget::MinVals
進(jìn)行取地址的操作,會(huì)導(dǎo)致MinVals去尋找定義,但是上面的代碼沒(méi)有在類(lèi)外定義MinVals,這就導(dǎo)致在鏈接階段報(bào)錯(cuò),找不到Widget::MinVals
的定義。
? 靜態(tài)const數(shù)據(jù)成員的這個(gè)特點(diǎn)在完美轉(zhuǎn)發(fā)的場(chǎng)景下也會(huì)帶來(lái)同樣的問(wèn)題,完美轉(zhuǎn)發(fā)是通過(guò)通用引用來(lái)轉(zhuǎn)發(fā)參數(shù),通用引用本質(zhì)上是指針,因此在這個(gè)場(chǎng)景下如果沒(méi)有在類(lèi)外定義MinVals也會(huì)在鏈接的時(shí)候報(bào)錯(cuò)。
template<typename T>void fwd(T&& param) { // 這里是通用引用 f(std::forward<T>(param));}fwd(Widget::MinVals);? 上面的fwd通過(guò)完美轉(zhuǎn)發(fā)processVal但是編譯失敗,因?yàn)槟0娴念?lèi)型推導(dǎo)無(wú)法推導(dǎo)出processVal的類(lèi)型。這就是在函數(shù)重載的場(chǎng)景下模版類(lèi)型推導(dǎo)失敗的例子。同樣如果使用函數(shù)模版替換模版重載的話,模版類(lèi)型推導(dǎo)依然失敗,無(wú)法推導(dǎo)出函數(shù)模版的類(lèi)型。
template<typename T>T workOnVal(T param) { .....}fwd(workOnVal);解決上面這兩個(gè)問(wèn)題也很簡(jiǎn)單,就是主動(dòng)賦予函數(shù)和函數(shù)模版的類(lèi)型,代碼如下:
using ProcessFuncType = int(*)(int);ProcessFuncType processValPtr = processVal;fwd(processValPtr);fwd(static_cast<ProcessFuncType>(workOnVal));最后一種完美轉(zhuǎn)發(fā)失效的情況是位域,代碼如下:
struct ipv4Header { std::uint32_t version:4, IHL:4, DSCP:6, ECN:2, totalLength:16; .....};void f(std::size_t sz);IPv4Header h;f(h.totolLength);fwd(h.totolLength); //編譯失敗? 上面的代碼中位域h.totolLength
被傳遞給f的時(shí)候可以正常工作,當(dāng)傳遞給fwd完美轉(zhuǎn)發(fā)的時(shí)候編譯失敗,因?yàn)閒wd是一個(gè)模版,模版的參數(shù)是通用引用,本質(zhì)上是一個(gè)引用,C++標(biāo)準(zhǔn)規(guī)定一個(gè)非const的引用無(wú)法引用一個(gè)位域字段。這個(gè)規(guī)定也是有原因的,因?yàn)槲挥蚩赡苤皇且粋€(gè)int的部分字節(jié),沒(méi)有一個(gè)確定的地址,沒(méi)辦法通過(guò)指針指向位域,引用本質(zhì)上就是指針,自然沒(méi)辦法引用位域了。既然沒(méi)辦法對(duì)一個(gè)位域進(jìn)行引用那么可以通過(guò)拷貝位域的值后然后再進(jìn)行完美轉(zhuǎn)發(fā),代碼如下:
? 總結(jié)來(lái)說(shuō),完美轉(zhuǎn)發(fā)失敗的例子總共有三類(lèi),第一類(lèi)就是模版無(wú)法進(jìn)行有效的類(lèi)型推導(dǎo),例如上文提到的函數(shù)重載,模版重載,統(tǒng)一初始化等,第二類(lèi)則是模版推導(dǎo)的類(lèi)型是錯(cuò)誤的,例如上文中提到的0或者NULL 作為空指針,第三類(lèi)則是引用無(wú)法指向傳遞過(guò)來(lái)的參數(shù),例如上文中提到的位域和靜態(tài)const數(shù)據(jù)成員。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注