4 設(shè)計與聲明
條款18:讓接口容易被正確使用雅采,不易被誤用
理想上击碗,如果客戶企圖使用某個接口而卻沒有獲得他所預(yù)期的行為把跨,這個代碼不該通過編譯丧没;如果代碼通過了編譯鹰椒,它的作為就該是客戶所想要的。欲開發(fā)一個“容易被正確使用呕童,不容易被誤用”的接口篡帕,首先必須考慮客戶可能做出什么樣的錯誤哈街。
- 許多客戶端錯誤可以因為導(dǎo)入新類型而獲得預(yù)防窍荧。在防范“不值得擁有的代碼”上浸间,類型系統(tǒng)是你的主要同盟國套蒂。
struct Day {
explicit Day(int d) //explicit 避免隱式的轉(zhuǎn)換钞支。
:val(d) {}
int val;
};
... ...
class Date {
public:
Date(const Month& m, const Day& d, const Year& y);
... ...
}
// 使用
Date d(Month(3), Day(30), Year(1995));
對日期進行類似的類型封裝茫蛹,能有效地避免不恰當(dāng)?shù)娜掌谫x值。
對值的限制可通過枚舉方法實現(xiàn)烁挟。
“除非有好的理由婴洼,否則應(yīng)該盡量令你的類型(定義的類)的行為與內(nèi)置類型一致”。
在資源管理方面撼嗓,讓函數(shù)返回一個資源的指針改為返回一個智能指針柬采,以把delete責(zé)任推給智能指針。
例如:
std::tr1::shared_ptr<Investment> createInvestment();
這便實質(zhì)上強迫客戶將返回值存儲于一個tr1::shared_ptr內(nèi)且警,幾乎消除了忘記刪除底部Investment對象的可能性粉捻。
- tr1::shared_ptr有一個特別好的性質(zhì)是:它會自動使用它的“每個指針專屬的刪除器”,因而消除另一個潛在的客戶錯誤:所謂的“cross-DLL problem”斑芜。這個問題發(fā)生于“對象在動態(tài)鏈接程序庫(DLL)中被new創(chuàng)建肩刃,卻在另一個DLL內(nèi)被delete銷毀”。tr1::shared_ptr沒有這個問題杏头,因為它缺省的刪除器是來自“tr1::shared_ptr誕生所在的那個DLL”的delete盈包。
note:
- 好的接口很容易被正確使用,不容易被誤用醇王。你應(yīng)該在你的所有接口中努力達(dá)成這些性質(zhì)呢燥。
- “促進正確使用”的辦法包括接口的一致性,以及與內(nèi)置類型的行為兼容寓娩。
- “阻止誤用”的辦法包括建立新類型叛氨、限制類型上的操作,束縛對象值棘伴,以及消除客戶的資源管理責(zé)任寞埠。
- tr1::shared_ptr支持定制刪除器。這可防范DLL問題排嫌,可被用來自動解除互斥鎖(條款14)等等畸裳。
條款19:設(shè)計 class 猶如設(shè)計 type
C++就像在其它面向?qū)ο缶幊陶Z言一樣,當(dāng)你定義一個新class淳地,也就定義了一個新type怖糊。這意味著你并不只是類的設(shè)計者,更是類型的設(shè)計者颇象。重載函數(shù)和操作符伍伤、控制內(nèi)存的分配和歸還、定義對象的初始化和終結(jié)......全部在你手上遣钳。
設(shè)計一個良好的類扰魂,或者稱作類型,需要考慮以下設(shè)計規(guī)范:
- 新類型的對象應(yīng)該如何被創(chuàng)建和銷毀?
- 對象的初始化和對象的賦值該有什么樣的差別劝评?
- 新類型的對象如果被passed by value(值傳遞)姐直,意味著什么?
- 什么是新類型的“合法值”蒋畜?
- 你的新類型需要配合某個繼承圖系嗎声畏?
- 你的新類型需要什么樣的轉(zhuǎn)換?
- 什么樣的操作符和函數(shù)對此新類型而言是合理的姻成?
- 什么樣的標(biāo)準(zhǔn)函數(shù)應(yīng)該駁回插龄?
- 誰該取用新類型的成員?
- 什么是新類型的“未聲明接口”科展?
- 你的新類型有多少一般化均牢?
- 你真的需要一個新類型嗎?
note:
class的設(shè)計就是type的設(shè)計才睹。在定義一個新type之前徘跪,請確定你已經(jīng)考慮過本條款覆蓋的所有討論主題。
條款20:寧以 pass-by-reference-to-const 替換 pass-by-value
缺省情況下C++以by value方式傳遞對象至(或來自)函數(shù)砂竖。除非你另外指定真椿,否則函數(shù)參數(shù)都是以實際實參的副本為初值,而調(diào)用端所獲得的亦是返回值的一個副本乎澄。
這些副本由對象的拷貝構(gòu)造函數(shù)產(chǎn)生突硝。所以在以對象為pass by value時,可能會調(diào)用相應(yīng)的構(gòu)造函數(shù)(成員對象的構(gòu)造置济、基類對象的構(gòu)造)解恰,然后調(diào)用對應(yīng)的析構(gòu)函數(shù)。所以以by value的形式傳遞開銷還是比較大的浙于。
如果我們用pass-by-reference-to-const护盈,例如:
bool validateStudent(const Student& s); //const,希望別對傳入對象進行不恰當(dāng)?shù)男薷模?
這種傳遞方式效率高得多:沒有任何構(gòu)造函數(shù)或析構(gòu)函數(shù)被調(diào)用羞酗,因為沒有任何新對象被創(chuàng)建腐宋。
以傳引用方式傳遞參數(shù)也可以避免對象切割問題:即當(dāng)一個派生類對象以傳值的方式傳遞并被視為一個基類對象,基類對象的拷貝構(gòu)造函數(shù)會被調(diào)用檀轨,而“造成此對象的行為像個派生類對象”的那些特化性質(zhì)全被切割掉了胸竞,僅僅留下了基類對象。這一般不是你想要的参萄。解決切割問題的辦法卫枝,就是以by-reference-to-const的方式傳遞。
所以我們一般的做法應(yīng)該是這樣:內(nèi)置對象和STL的迭代器和函數(shù)對象讹挎,我們一般以傳值的方式傳遞校赤,而其它的任何東西都以傳引用的方式傳遞吆玖。
note:
- 盡量以pass-by-reference-to-const替代pass-by-value。前者通常比較高效马篮,并可避免切割問題沾乘。
- 以上規(guī)則并不適用于內(nèi)置類型,以及STL的迭代器和函數(shù)對象积蔚。對它們而言意鲸,pass-by-value往往比較適當(dāng)烦周。
條款21:必須返回對象時尽爆,別妄想返回其reference
當(dāng)我們領(lǐng)悟條款20中傳值的開銷后,總是避免于少用傳值读慎,然而在返回對象時漱贱,要格外小心了,因為你可能:傳遞一些引用或指針指向其實已經(jīng)不存在的對象夭委。這可不是件好事幅狮。
任何時候看到一個reference聲明式,你都應(yīng)該立刻問自己株灸,它的另一個名稱是什么崇摄?
函數(shù)創(chuàng)建新對象的途徑有二:在棧空間和堆空間
棧(stack)上:即在函數(shù)內(nèi)的局部變量慌烧。局部變量在函數(shù)返回后就沒有存在的意義逐抑,若還對它“念念不忘”,將帶來災(zāi)難性后果屹蚊。所以傳引用在棧上不可行厕氨。
堆(heap)上:在堆上構(gòu)造一個對象,并返回汹粤∶看似可行,也埋下了資源泄漏的危險嘱兼。誰該對這對象實施delete呢国葬?別把這種對資源的管理寄托完全寄托于用戶。所以傳引用在堆上不可行芹壕。
可能還有一種想法:把“讓返回的引用指向一個被定義于函數(shù)內(nèi)部的靜態(tài)對象”汇四。出于我們對多線程安全性的疑慮,以及當(dāng)線程中兩個函數(shù)對單份對象的處理也可能帶來不可測行為哪雕。所以靜態(tài)對象也是不可行的船殉。
一個“必須返回新對象”的函數(shù)的正確寫法是:就讓那個函數(shù)返回一個新對象。
編譯器實現(xiàn)者實行最優(yōu)化斯嚎,用以改善產(chǎn)出碼的效率卻不改變其觀察的行為利虫。所以我們還是老老實實的返回一個對象吧挨厚。
note:
絕不要返回pointer或reference指向一個local stack對象,或返回reference指向一個heap-allocated對象糠惫,或返回pointer或reference指向一個local static對象而有可能同時需要多個這樣的對象疫剃。