========================
Effective C++ 設(shè)計(jì)與聲明
書(shū)作者:Scott Meyers
原筆記作者:Justin
========================
Item 18 : 接口應(yīng)該容易被正確使用,不易被誤用
--------------------------------------------------
tag:消除客戶(hù)的資源管理責(zé)任 tr1::shared_ptr cross-DLL problem
用戶(hù)使用接口時(shí)卻沒(méi)有獲得預(yù)期的行為,這個(gè)代碼不應(yīng)該通過(guò)編譯。
用錯(cuò)的可能有:
·調(diào)用接口時(shí)輸入了錯(cuò)誤的參數(shù)。
如(一個(gè)接受年、月、日為參數(shù)的接口函數(shù),用戶(hù)可以輕易給出各種錯(cuò)誤的輸入),
解決辦法:用對(duì)象來(lái)約束參數(shù)輸入的范圍(不接受簡(jiǎn)單的整數(shù)作為輸入,而是Date、Mon、Year對(duì)象)
struct Day{ explicit Day(int d):val(d){} int val;}
class Month{
public:
static Month May(){ return Month(5); }
private:
explicit Month(int m);
}
Date d(Month::Mar(),Day(20),Year(1995));
·用常規(guī)的用法調(diào)用“特別”設(shè)計(jì)的接口。所以需要盡可能的把自己的設(shè)計(jì)往常規(guī)上靠:數(shù)據(jù)對(duì)象的行為要盡可能符合內(nèi)建對(duì)象(比如int)的行為;接口的名字和意義要盡可能一致(比如STL中的容器基本都有一個(gè)叫做size的返回容器大小的接口)……這樣做鼓勵(lì)用戶(hù)去正確的看待和使用你的接口。
·忘了處理調(diào)用接口后的遺留問(wèn)題。因此不要讓用戶(hù)去“記得”做一些事情。
如設(shè)計(jì)一個(gè)接口返回一個(gè)指向某新建對(duì)象的指針,該接口的用戶(hù)需要“記得”去釋放這個(gè)指針?biāo)傅膶?duì)象:如果用戶(hù)忘了釋放或釋放了好幾次,后果就是@#¥%
解決的辦法之一是讓該接口返回一個(gè)智能指針(嗯……印象模糊了?去看Item14),這樣用戶(hù)用完了就可以“忘記”這個(gè)指針:它自己會(huì)處理后事。
·所謂的“跨DLL問(wèn)題”(cross DLL problem):在一個(gè)DLL中new一個(gè)對(duì)象,然后對(duì)象被傳到另外一個(gè)DLL里被delete。大師推薦用shared_ptr因?yàn)樗鉀Q了這個(gè)問(wèn)題。
代價(jià):額外對(duì)象的創(chuàng)建和銷(xiāo)毀需要時(shí)間空間。比如boost的shared_ptr就是普通指針的兩倍大小,還有額外的對(duì)象操作時(shí)間+過(guò)程動(dòng)態(tài)內(nèi)存分配等。
實(shí)際上有些底層代碼根本沒(méi)這個(gè)資本提供這樣的“豪華裝備”,不過(guò)有這樣的思想還是很重要D……
Item 19 :設(shè)計(jì)class猶如設(shè)計(jì)type
----------------------------------------
tag:class design
·小心設(shè)計(jì)類(lèi)的創(chuàng)建和銷(xiāo)毀方式。比如說(shuō)Item8和Item16
·認(rèn)真考慮如何區(qū)分類(lèi)的構(gòu)造函數(shù)和賦值(assignment)操作符。即初始化與賦值的差別。
·注意實(shí)現(xiàn)類(lèi)的傳值(passed by value)。這個(gè)實(shí)際上是在說(shuō)要注意拷貝構(gòu)造函數(shù)的實(shí)現(xiàn)。
·切勿忽略類(lèi)對(duì)非法輸入的處理。其實(shí)是要注意各種出錯(cuò)情況,是否需要拋出異常以及如何實(shí)現(xiàn)異常處理。
·需要審視類(lèi)所在的繼承體系。
如果該類(lèi)有父類(lèi),那么必定要受到父類(lèi)的一些限制,特別是函數(shù)是否為虛構(gòu);如果該類(lèi)有子類(lèi),那么就要考慮是不是一些函數(shù)需要定義為虛函數(shù),比如說(shuō)析構(gòu)函數(shù)。
·謹(jǐn)慎實(shí)現(xiàn)類(lèi)對(duì)象與其他類(lèi)型對(duì)象的轉(zhuǎn)換。這一點(diǎn)稍有些復(fù)雜:如果有將T1轉(zhuǎn)換為T(mén)2的需求,就有隱式轉(zhuǎn)換和顯式轉(zhuǎn)換兩種方式。
對(duì)于前者,可以編寫(xiě)一個(gè)(隱式的)轉(zhuǎn)換函數(shù)(參考Item15里面的隱式轉(zhuǎn)換咯~),或者是通過(guò)額外編寫(xiě)一個(gè)T2的構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)T1向T2的轉(zhuǎn)換。
對(duì)于后者,Scott說(shuō)寫(xiě)一個(gè)(顯式的)轉(zhuǎn)換函數(shù)就可以了。(同樣,在Item15里也有顯式轉(zhuǎn)換函數(shù)的例子)
·需要考慮該類(lèi)需要參與哪些運(yùn)算。很明顯,如果需要參與A運(yùn)算就要相應(yīng)定義類(lèi)的A運(yùn)算符函數(shù)。大師在這里提的另外一點(diǎn)是,這些運(yùn)算符號(hào)函數(shù)有些應(yīng)該是成員函數(shù),有些不應(yīng)該。原因在Item23、24、26【555我還沒(méi)看到,留空】
·不要提供不應(yīng)該暴露的標(biāo)準(zhǔn)函數(shù)。這里的標(biāo)準(zhǔn)函數(shù)指的是構(gòu)造/析構(gòu)/拷貝等等可能由編譯器“自愿”為你生成的函數(shù),如果不希望它們中的一些被外界調(diào)用,就聲明為私有(private)。沒(méi)印象了?降級(jí)到Item6重新學(xué)習(xí)~
·注意設(shè)計(jì)類(lèi)成員的訪問(wèn)權(quán)限。公有(public)、保護(hù)(protected)、私有(private)應(yīng)該用哪一種?有沒(méi)有需要定義友元?或者是干脆來(lái)一個(gè)類(lèi)中類(lèi)?都需要考慮。
·認(rèn)真審查類(lèi)的隱性限制。性能上的要求、使用資源的限制或是出錯(cuò)時(shí)的處理都有可能影響到類(lèi)的具體設(shè)計(jì)和實(shí)現(xiàn)。
·謹(jǐn)慎考慮類(lèi)的適用范圍。也就是說(shuō)如果某個(gè)設(shè)計(jì)會(huì)可能用在很多方面,適用于許多不同的實(shí)際對(duì)象。也許這個(gè)時(shí)候你需要設(shè)計(jì)的不是一個(gè)類(lèi),而是一個(gè)類(lèi)模板。
最后一點(diǎn)其實(shí)應(yīng)該放在第一位:你真的需要定義一個(gè)類(lèi)嗎?如果僅僅是在繼承某類(lèi)的基礎(chǔ)上加一兩個(gè)成員,是不是非成員函數(shù)或模板就已經(jīng)夠了捏?
第19招其實(shí)更像是個(gè)check list,在準(zhǔn)備動(dòng)手設(shè)計(jì)之前,一一比對(duì)打勾劃叉,應(yīng)該可以提前避免很多人間慘劇……
Item 20 : 用 傳const引用 替換 傳值
-------------------------------------------
tag: const引用 值傳遞
C++傳遞對(duì)象的時(shí)候默認(rèn)是傳值的(pass-by-value),而這樣的傳遞自然是昂貴的:這當(dāng)中包含了臨時(shí)對(duì)象的構(gòu)造/析構(gòu),以及臨時(shí)對(duì)象中的對(duì)象的構(gòu)造/析構(gòu),運(yùn)氣背點(diǎn)還可能有對(duì)象中的對(duì)象中的對(duì)象的構(gòu)造/析構(gòu)……(有好的不學(xué),去學(xué)C@#¥%)
·相對(duì)于傳“值”,一個(gè)更好的替代方法是傳“const引用”(pass-by-reference-to-const)。
·傳值與傳指針的一個(gè)區(qū)別是,通過(guò)傳值傳遞的對(duì)象并不是原來(lái)的對(duì)象,而是一個(gè)復(fù)制品,所以隨便你打它罵它,真身都不會(huì)受到影響。
·而通過(guò)傳指針的對(duì)象和原來(lái)的對(duì)象就是同一家伙,改動(dòng)一個(gè)另外一個(gè)也受到相同的影響。而這有時(shí)候并不是我們想要的結(jié)果。
·考慮到傳值代價(jià)太高,傳“const引用”就成了一個(gè)很好的替代品。
·傳“const引用”的另外一個(gè)好處在于避免了“剝皮問(wèn)題”(slicing problem,侯捷大師的版本是“對(duì)象切割問(wèn)題”,我用這個(gè)中文名字是為了更容易記住:))
用傳值方式傳參的函數(shù),如果某參數(shù)的類(lèi)型是一個(gè)父類(lèi)對(duì)象,而實(shí)際傳遞的參數(shù)是一個(gè)子類(lèi)對(duì)象,只有該對(duì)象的父類(lèi)部分會(huì)被構(gòu)造并傳遞到函數(shù)中,子類(lèi)部分的成員,作為父類(lèi)對(duì)象的“皮”,就被血淋淋的剝掉了……
而如果用傳“const引用”方式,就沒(méi)有這種慘無(wú)人道的狀況:本來(lái)父類(lèi)的指針就可以用來(lái)指向一個(gè)子類(lèi)對(duì)象,天經(jīng)地義。
例外:對(duì)于內(nèi)置類(lèi)型(bulit-in type)對(duì)象以及STL中的迭代器、函數(shù)對(duì)象,Scott還是建議使用傳值方式傳遞,原因是他們本來(lái)就是被設(shè)計(jì)成適合傳值傳遞的。
(個(gè)人觀點(diǎn):大師說(shuō):“……it's not unreasonable to choose pass-by-value。”,注意這里有句潛臺(tái)詞:其實(shí)對(duì)以上類(lèi)型用傳“const引用”方式傳遞也是可以的。)
如果你認(rèn)為上面兩種情況可以用傳值傳遞是因?yàn)樗鼈儯热缯f(shuō)內(nèi)置類(lèi)型對(duì)象,的大小本來(lái)就小,進(jìn)而得出小數(shù)據(jù)類(lèi)型就可以用傳值傳遞,就打錯(cuò)特錯(cuò)了。小對(duì)象的構(gòu)造/析構(gòu)過(guò)程完全可能很恐怖。
再退一步,哪怕某個(gè)類(lèi)型很小,它的構(gòu)造/析構(gòu)函數(shù)也簡(jiǎn)單到可以忽略不計(jì),我們還是不能以此斷定可以用傳值傳遞這種類(lèi)型的對(duì)象:因?yàn)榫幾g器往往會(huì)做出一些蠢事。書(shū)中的一個(gè)例子是,對(duì)于一些編譯器可以接受把一個(gè)double類(lèi)型對(duì)象存入寄存器,但是如果你給它一個(gè)只有一個(gè)double成員的對(duì)象交給它,它卻拒絕將該對(duì)象存入寄存器。(什么事讓編譯器插一手,不是問(wèn)題也有了問(wèn)題……)
最后還有個(gè)理由,雖然某對(duì)象現(xiàn)在很小,可是隨著社會(huì)的發(fā)展人類(lèi)的進(jìn)步,有可能兩年后它就會(huì)變成一個(gè)龐然大物,到時(shí)候用傳值也會(huì)變得不合適。
·除了內(nèi)置類(lèi)型和STL的迭代器、函數(shù)對(duì)象外,其他的對(duì)象傳遞時(shí),用傳“const引用”代替?zhèn)髦蛋伞?
Item 21: 該換回對(duì)象時(shí)別返回它的reference
---------------------------------------------------
tag:
如果一個(gè)函數(shù)可能返回一個(gè)對(duì)原來(lái)不存在的對(duì)象的引用,那么函數(shù)就要自己去創(chuàng)建這個(gè)對(duì)象:要么在棧上(stack)要么在堆上(heap)。
第一種情況中,函數(shù)中定義了局部對(duì)象,然后返回對(duì)該對(duì)象的引用。對(duì)象在函數(shù)結(jié)束后自動(dòng)銷(xiāo)毀,引用指向無(wú)效的地址。
第二種情況,函數(shù)使用new動(dòng)態(tài)創(chuàng)建了一個(gè)對(duì)象,然后返回對(duì)該對(duì)象的引用。粗看沒(méi)有問(wèn)題,因?yàn)檫@個(gè)返回的引用還是有效的。
但是細(xì)想就會(huì)發(fā)現(xiàn):我們能確保這個(gè)對(duì)象被正確的收回(delete)嗎?
書(shū)中舉了一個(gè)很好的例子:一個(gè)*運(yùn)算符函數(shù),接受兩個(gè)乘法運(yùn)算數(shù),返回一個(gè)積。
如果在函數(shù)中動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象來(lái)存儲(chǔ)乘積,并返回對(duì)這個(gè)新對(duì)象的引用,那么下面的計(jì)算就會(huì)帶來(lái)內(nèi)存泄漏:
Y=A*B*C
因?yàn)樵谶@個(gè)“連續(xù)”乘法中,有兩個(gè)“乘積對(duì)象”被創(chuàng)建,但是我們丟失了第一次乘法創(chuàng)建的對(duì)象的指針。
所以這樣的做法是不妥的。
也許大師有被問(wèn)過(guò):那么對(duì)于第一種情況我們可不可以返回一個(gè)靜態(tài)(static)對(duì)象的引用?書(shū)中用了同樣的例子來(lái)回答:NO。
if (A*B == C*D) {//..}
如果返回靜態(tài)對(duì)象的引用,上面的判斷語(yǔ)句永遠(yuǎn)得到true值,因?yàn)?#8220;==”號(hào)兩邊的運(yùn)算結(jié)果是指向同一塊數(shù)據(jù)的引用。
不知道是不是后面又有刨根問(wèn)題的學(xué)生追問(wèn):那我能不能用一個(gè)靜態(tài)對(duì)象的數(shù)組來(lái)存放不同此運(yùn)算的結(jié)果?大師懶得舉例子了,我猜想也沒(méi)必要:這樣的方案帶來(lái)的副作用及其開(kāi)銷(xiāo)本身就已經(jīng)大于原來(lái)要解決的問(wèn)題了吧?
不要嘗試在函數(shù)中返回對(duì)局部對(duì)象(存儲(chǔ)于棧)的引用,也不要對(duì)動(dòng)態(tài)創(chuàng)建的對(duì)象(存儲(chǔ)于堆)做同樣的蠢事,而如果真有打算、非常渴望、十分想要返回對(duì)靜態(tài)對(duì)象的引用,在這么做之前也要考慮清楚會(huì)不會(huì)有上面例子中的情況出現(xiàn)。
至少,返回對(duì)象不會(huì)有上面列出的種種錯(cuò)誤危險(xiǎn),僅僅是有可能帶來(lái)創(chuàng)建額外對(duì)象的開(kāi)銷(xiāo)而已,而這個(gè)開(kāi)銷(xiāo)的可能還有可能被排除,如果你用的編譯器足夠聰明的話(huà)。
Item 22: 將成員變量聲明為private
-----------------------------------------------------
如果數(shù)據(jù)成員都是私有的,那么訪問(wèn)這些成員就只能通過(guò)函數(shù)進(jìn)行。于是用戶(hù)就不需要費(fèi)心考慮到底要用什么方式去訪問(wèn)數(shù)據(jù)成員:因?yàn)橹挥卸x了的函數(shù)可以用。
通過(guò)定義數(shù)據(jù)成員為私有,可以實(shí)現(xiàn)函數(shù)來(lái)設(shè)計(jì)、約束或禁止對(duì)這些成員的各種訪問(wèn)(讀/寫(xiě)等)。而如果將其設(shè)為公有(public),你將無(wú)法得知你的成員會(huì)被誰(shuí)改動(dòng),也不知道會(huì)是怎樣的改動(dòng)。
而更重要的好處是封裝(encapsulation):可以方便的通過(guò)修改函數(shù)來(lái)改變成員的訪問(wèn)方式;在成員被訪問(wèn)時(shí)通知其他對(duì)象;實(shí)現(xiàn)多線程中的同步等等。
封裝的好處究其本質(zhì),是通過(guò)對(duì)用戶(hù)隱藏?cái)?shù)據(jù)成員來(lái)保證類(lèi)行為的一致性(class invariant)。因?yàn)榻涌诒怀蓡T訪問(wèn)函數(shù)限制了,類(lèi)的作者也為自己日后修改類(lèi)的實(shí)現(xiàn)留了后路:如果所有的成員都是公有的,對(duì)任何代碼的修改都有可能影響到外界的使用。(因此Scott說(shuō)“Public means unencapsulated, and practically speaking, unencapsulated means unchangeable, especially for classes that are widely used.”)
那么可不可以聲明為保護(hù)(protected)呢?其實(shí)道理和前面的公有是一樣的。公有的成員對(duì)類(lèi)的外部完全開(kāi)放,而保護(hù)的成員對(duì)類(lèi)的繼承者完全開(kāi)放。這個(gè)就像兩個(gè)區(qū)間:(-infinity, +infinity) 和 (0, +infinity),兩者的大小是一樣的。
從分裝的角度,只有兩種訪問(wèn)級(jí)別:私有,及其他。
Item 23: 用 non-member、non-friend 替換 member 函數(shù)
------------------------------------------------------
從面向?qū)ο蟮慕嵌葋?lái)看,非成員函數(shù)更有利于數(shù)據(jù)的封裝。
一個(gè)數(shù)據(jù)成員被越少的代碼訪問(wèn)到,該成員的封裝程度就越高。越少函數(shù)可以直接訪問(wèn)一個(gè)數(shù)據(jù)成員,該成員的封裝程度就越高。
類(lèi)的數(shù)據(jù)成員應(yīng)該定義為私有。如果這個(gè)前提成立,那么能夠訪問(wèn)一個(gè)數(shù)據(jù)成員的函數(shù)便只能是該類(lèi)的成員函數(shù),或是友元函數(shù)。
于是為了更好的封裝數(shù)據(jù),在可以完成相同功能的前提下,應(yīng)該優(yōu)先考慮使用非成員并且非友元函數(shù)。
這里的“非成員并且非友元函數(shù)”是針對(duì)數(shù)據(jù)成員所在的類(lèi)而言的,也就是說(shuō)這個(gè)函數(shù)完全可以是其他類(lèi)的成員,只要是不能直接訪問(wèn)那個(gè)數(shù)據(jù)成員就可以。
從靈活性上來(lái)說(shuō),非成員函數(shù)更少編譯依賴(lài)(compilation dependency),也就更利于類(lèi)的擴(kuò)展。
例:一個(gè)類(lèi)可能有多個(gè)成員函數(shù),可能有一個(gè)函數(shù)需要A.h,另外一個(gè)函數(shù)要包含B.h,那么在編譯這個(gè)類(lèi)時(shí)就需要同時(shí)包含A.h和B.h,也就是說(shuō)該類(lèi)同時(shí)依賴(lài)兩個(gè)頭文件。
如果使用非成員函數(shù),這個(gè)時(shí)候就可以把這些依賴(lài)關(guān)系不同的函數(shù)分別寫(xiě)在不同的頭文件中,有可能這個(gè)類(lèi)在編譯時(shí)就不需要再依賴(lài)A.h或是B.h了。
把這些非成員函數(shù)分散定義在不同頭文件中的同時(shí),需要用namespace關(guān)鍵字把它們和需要訪問(wèn)的類(lèi)放在一起。
// code in class_a.h
namespace AllAboutClassA {
class ClassA { // ..};
// ..
}
// code in utility_1.h
// ..
namespace AllAboutClassA {
void WrapperFunction_1() { // ..};
// ..
}
// ..
// code in utility_2.h
// ..
namespace AllAboutClassA {
void WrapperFunction_2() { // ..};
// ..
}
// ..
這樣一來(lái),雖然這些非成員和類(lèi)不“住在”一個(gè)頭文件里,它們的“心”還是在一起的(在同一個(gè)名字空間, namespace, 中)。
如果有需要添加新的非成員函數(shù),我們要做的只是在相同的名字空間中定義這些函數(shù)就可以,那個(gè)類(lèi)絲毫不會(huì)被影響,也即所謂的易擴(kuò)展性吧。
對(duì)于類(lèi)的用戶(hù)來(lái)說(shuō),這樣的實(shí)現(xiàn)方式(指用非成員函數(shù))就更加合理:
因?yàn)樽鳛轭?lèi)的用戶(hù),需要擴(kuò)展類(lèi)的時(shí)候又不能去修改別人的類(lèi)(版權(quán)?安全性?或者根本就沒(méi)有源碼?),就算是通過(guò)繼承該類(lèi)的方式也不能訪問(wèn)父類(lèi)的私有數(shù)據(jù)。
Item 24: 若所有參數(shù)皆需類(lèi)型轉(zhuǎn)換,為此函數(shù)采用 non-member 函數(shù)
------------------------------------------------------
tag: operator
若需要為某個(gè)函數(shù)的所有參數(shù)(包括被this指針?biāo)傅哪莻€(gè)隱喻參數(shù))進(jìn)行類(lèi)型轉(zhuǎn)換,這個(gè)函數(shù)應(yīng)該設(shè)為 non-member.
---------------------
class Rational{
public:
Rational(int num = 0, int denominator = 1); //構(gòu)造函數(shù)刻意不為 explicit; 允許 int-to-Rational 隱式轉(zhuǎn)換
...
const Rational operator* (const Rational& rhs) const;
}
Rational oneHalf(1, 2);
result = oneHalf * 2; //right → result = oneHalf.operator*(2);
result = 2 * oneHalf; //wrong! → result = 2.operator*(oneHalf); → result = operator*(2, oneHalf);
---------------------
const Rational operator*(const Rational& lhs, const Rational& rhs){..} //non-member function.
若函數(shù)不該成為member,不一定要成為friend.
Item 25: 考慮寫(xiě)出一個(gè)不拋異常的 swap 函數(shù)
------------------------------------------------------
tag: 異常處理
·當(dāng) std::swap 對(duì)你的類(lèi)型效率不高時(shí),提供一個(gè) swap 成員函數(shù),并確定函數(shù)不拋出異常。
·如果你提供一個(gè) member swap,也該提供一個(gè) non-member swap 用來(lái)調(diào)用前者,
對(duì)于 classes(非 templates),請(qǐng)?zhí)鼗?std::swap;
·調(diào)用 swap 時(shí)應(yīng)針對(duì) std::swap 使用using 聲明式,然后調(diào)用 swap 并不帶任何“命名空間資格修飾”。
·為“用戶(hù)定義類(lèi)型”進(jìn)行std templates 全特化是好的,但千萬(wàn)不要嘗試在 std 內(nèi)加入某些對(duì) std 而言全新的東西。
std::swap的缺省實(shí)現(xiàn):用了一個(gè)中間臨時(shí)對(duì)象,然后兩兩交換。
缺省的方法很簡(jiǎn)單,而在一些情況下卻也很耗資源:比如需要交換的是一個(gè)很復(fù)雜龐大的對(duì)象時(shí),創(chuàng)建/拷貝大量數(shù)據(jù)會(huì)使得這種swap的效率顯得非常低下。
--------------------------
更加適應(yīng)的實(shí)現(xiàn)思路:
在類(lèi)/模板類(lèi)(class/class template)中定義一個(gè)公有的swap成員函數(shù),這個(gè)函數(shù)負(fù)責(zé)實(shí)現(xiàn)真正的交換操作。同時(shí),這個(gè)函數(shù)不能拋出異常。
用成員是因?yàn)榻粨Q操作中可能會(huì)需要訪問(wèn)/交換類(lèi)的私有成員;用公有(public)來(lái)限制是為了給下面的輔助函數(shù)(wrapper function)提供接口。
至于不能拋出異常,有兩個(gè)原因:
一是Item29中所提到的異常安全性(exception-safety)需要不拋出異常的swap來(lái)提供保障(更多細(xì)節(jié)就到拜讀29條的時(shí)候再記錄吧。)
二是一般而言高效的swap函數(shù)幾乎都是對(duì)內(nèi)置類(lèi)型的交換,而對(duì)內(nèi)置類(lèi)型的操作是不會(huì)拋出異常的。
1. 如果需要使用swap的是一個(gè)類(lèi)(而不是模板類(lèi)),就為這個(gè)類(lèi)全特化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)完成該類(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),用戶(hù)可以直接應(yīng)用同樣的swap函數(shù)進(jìn)行交換操作,而當(dāng)交換對(duì)象是需要特殊對(duì)待的AClass對(duì)象時(shí),也可以無(wú)差別的使用并得到預(yù)期的交換結(jié)果。
2. 如果我們需要交換的是模板類(lèi),那么就不能用全特化std::swap的方法了,偏特化的std::swap也行不通,因?yàn)椋?br> C++中不允許對(duì)函數(shù)進(jìn)行偏特化(只能對(duì)類(lèi)偏特化),也 就是說(shuō)不能寫(xiě)出下面的程序:
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)作,不允許往里頭添加任何新的模板、類(lèi)、方程,重載也不可以。
雖然可以像1.那樣寫(xiě)出全特化的模板函數(shù),但是企圖在std的名字空間添加以下重載的swap(這種重載變相實(shí)現(xiàn)了函數(shù)的偏特化)(雖然你可以通過(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è)變通的方法是在該模板類(lèi)所在的名字空間中編寫(xiě)一個(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的是模板@#¥%
pimpl也即pointer to implementation,當(dāng)需要交換兩個(gè)復(fù)雜且臃腫的對(duì)象時(shí),可以先用兩個(gè)指針?lè)謩e指向著兩個(gè)對(duì)象
之后對(duì)這些對(duì)象的操作,包括交換,就只需要通過(guò)這兩個(gè)指針來(lái)進(jìn)行(交換兩個(gè)指針的值便實(shí)現(xiàn)了對(duì)象的交換)。


