[原創(chuàng)文章歡迎轉(zhuǎn)載,但請(qǐng)保留作者信息]
Justin 于 2009-12-28
筆記寫到一半,有一些同學(xué)說(shuō)還是不要放在首頁(yè)了他們想讀自己會(huì)去找的。想想也有道理,假借大師的名義污染大眾的視線確實(shí)還是有點(diǎn)不厚道。
所以后面的筆記會(huì)放回新手區(qū)。噢~再見(jiàn)了首頁(yè)~
言歸正傳,記錄大師的第25堂課。
不知道std::swap函數(shù)是不是有那么重要(原文說(shuō)它是異常安全性編程 exception-safe programming的脊柱……),Scott專門用一個(gè)Item來(lái)說(shuō)明實(shí)現(xiàn)它需要考慮的各種因素。讀下來(lái)我倒是覺(jué)得文中提到的方法或技巧可以用到更多的地方,swap只不過(guò)是個(gè)典型罷了。
首先大師先是給出最經(jīng)典當(dāng)然也是最簡(jiǎn)單的swap函數(shù)實(shí)現(xiàn):用了一個(gè)中間臨時(shí)對(duì)象,然后兩兩交換。這也是std::swap的缺省實(shí)現(xiàn),如果這樣的swap已經(jīng)可以滿足需要,那么就不需要再費(fèi)心考慮,直接用就是了。我們就提前下課。
如果還在讀,說(shuō)明可能意識(shí)到:缺省的方法很簡(jiǎn)單,而在一些情況下卻也很耗資源:比如需要交換的是一個(gè)很復(fù)雜龐大的對(duì)象時(shí),創(chuàng)建/拷貝大量數(shù)據(jù)會(huì)使得這種swap的效率顯得非常低下。
于是,大師給出更加適應(yīng)的實(shí)現(xiàn)思路:
-
在類/模板類(class/class template)中定義一個(gè)公有的swap成員函數(shù),這個(gè)函數(shù)負(fù)責(zé)實(shí)現(xiàn)真正的交換操作。同時(shí),這個(gè)函數(shù)不能拋出異常。
用成員是因?yàn)榻粨Q操作中可能會(huì)需要訪問(wèn)/交換類的私有成員;用公有(public)來(lái)限制是為了給下面的輔助函數(shù)(wrapper function)提供接口。
至于不能拋出異常,有兩個(gè)原因:
- 一是Item29中所提到的異常安全性(exception-safety)需要不拋出異常的swap來(lái)提供保障(更多細(xì)節(jié)就到拜讀29條的時(shí)候再記錄吧。)
- 二是一般而言高效的swap函數(shù)幾乎都是對(duì)內(nèi)置類型的交換,而對(duì)內(nèi)置類型的操作是不會(huì)拋出異常的。
-
如果需要使用swap的是一個(gè)類(而不是模板類),就需要為這個(gè)類全特化std::swap,然后在這個(gè)特化版本中調(diào)用第一步中實(shí)現(xiàn)的swap函數(shù)。
class
?AClass{
public
:
???
void
?swap(AClass
&
?theOtherA)
???{
??????
using
?std::swap;?
//
這一句會(huì)在稍后的第3點(diǎn)提到
??????
//
?通過(guò)調(diào)用swap來(lái)完成該類的特有交換動(dòng)作
???}
//
..
}
namespace
?std{
???
//
在std名字域內(nèi)定義一個(gè)全特化的swap
???template
<>
?
//
這樣的定義說(shuō)明是全特化
???
void
?swap
<
AClass
>
?(?AClass
&
?a,?AClass
&
?b)
???{
??????a.swap(b);
???}
}
如此一來(lái),用戶可以直接應(yīng)用同樣的swap函數(shù)進(jìn)行交換操作,而當(dāng)交換對(duì)象是需要特殊對(duì)待的AClass對(duì)象時(shí),也可以無(wú)差別的使用并得到預(yù)期的交換結(jié)果。
-
“2.”中說(shuō)的是當(dāng)交換“類”時(shí)可以采取的辦法,而如果我們需要交換的是模板類,那么就不能用全特化std::swap的方法了。然而用偏特化的std::swap也行不通,因?yàn)椋?br />
-
C++中不允許對(duì)函數(shù)進(jìn)行偏特化(只能對(duì)類偏特化),也
就是說(shuō)不能寫出下面的程序:
namespace?std{???
???//?illegal?code?as?C++?doesn't?allow?partial?specialization?for?function?templates
???template<typename?T>
???void?swap<?AClassTemplate<T>?>(AClassTemplate<T>&?a,?AClassTemplate<T>&?b)
???{
??????a.swap(b);
???}
}
-
std名字空間中的內(nèi)容都是C++標(biāo)準(zhǔn)委員會(huì)的老大們定義的,為了保證std內(nèi)部代碼的正常運(yùn)作(同時(shí)為了向老大表示尊敬……),委員會(huì)以外的小輩們是不允許往里頭添加任何新的模板、類、方程或是其他的什么東東的,重載也不可以。因此,雖然可以像2.那樣寫出全特化的模板函數(shù),但是企圖在std的名字空間添加以下重載的swap(這種重載變相實(shí)現(xiàn)了函數(shù)的偏特化)是不被C++委員會(huì)同意的(雖然你可以通過(guò)編譯,但是會(huì)埋下隱患):
namespace?std{
???template?<typename?T>
???void?swap?(AClass<T>&?a,?AClass<T>&?b)
???{?a.swap(b);}
}
給自己的一個(gè)小提醒:因?yàn)楹瘮?shù)名swap后沒(méi)有<>,所以不是偏特化,而是對(duì)
namespace?std{
???template<class?_Ty>?inline
???void?swap(_Ty&?_X,?_Ty&?_Y)
???{/*..*/}
}
的重載而已。
基于上面兩個(gè)原因,一個(gè)變通的方法是在該模板類所在的名字空間中編寫一個(gè)非成員的函數(shù)模板來(lái)調(diào)用這個(gè)公有的接口:
namespace?AClassSpace{
???template?<typename?T>
???void?swap?(AClass<T>&?a,?AClass<T>&?b)
???{?a.swap(b);}
}
在限定的名字空間中實(shí)現(xiàn)函數(shù),是為了避免“污染”全局的名字空間。而且,不同的名字空間都可以使用一樣的函數(shù)名字而不會(huì)有沖突。
基于前面第23課所學(xué),使用非成員函數(shù)也是應(yīng)該的了。
至于為什么要函數(shù)模板,那就匪常D簡(jiǎn)單:因?yàn)橐粨Q的是模板@#¥%
本課超時(shí),理解上也許還有偏差。不過(guò),Scott老師額外講了pimpl手法作為友情贈(zèng)送:
pimpl也即pointer to implementation,第31課會(huì)繼續(xù)講解。不過(guò)說(shuō)來(lái)也簡(jiǎn)單:當(dāng)需要交換兩個(gè)復(fù)雜且臃腫的對(duì)象時(shí),可以先用兩個(gè)指針?lè)謩e指向著兩個(gè)對(duì)象,之后對(duì)這些對(duì)象的操作,包括交換,就只需要通過(guò)這兩個(gè)指針來(lái)進(jìn)行(交換兩個(gè)指針的值便實(shí)現(xiàn)了對(duì)象的交換)。
參考文章:
Why Not Specialize Function Templates?
Template Specialization and Partial Template Specialization