Effective C++学习第八天
條款26:盡可能延后變量定義式的出現(xiàn)時(shí)間
? ? ? ? ? 當(dāng)你定義了一個(gè)變量,如果在使用變量之前出現(xiàn)異常,那么你得承受一次構(gòu)造成本和析構(gòu)成本,而且你沒有使用該變量;本條款給出的建議是延遲變量的定義,直到非得使用該變量的前一刻為止,甚至應(yīng)該嘗試延后這份定義知道能夠給它初值實(shí)參為止;這樣不僅可以避免構(gòu)造(析構(gòu))非必要的對(duì)象,還可以避免無意義的default構(gòu)造行為;
條款27:盡量少做轉(zhuǎn)型動(dòng)作
? ? ? ? ? 常見C風(fēng)格的類型轉(zhuǎn)換:(T)expression? ? ? ? ? ? ? ? ?函數(shù)風(fēng)格的轉(zhuǎn)型? ?T(expression)? ? ? //舊式轉(zhuǎn)型
? ? ? ? ? C++提供的四種新式轉(zhuǎn)型:
? ? ? ? ? ? ? ? const_cast<T>(expression);//去除對(duì)象常量性;
? ? ? ? ? ? ? ? dynamic_cast<T>(expression);//對(duì)象安全向下轉(zhuǎn)型,用于繼承
? ? ? ? ? ? ? ? reinterpret_cast<T>(expression);
? ? ? ? ? ? ? ? static_cast<T>(expression);//隱式轉(zhuǎn)型
盡量使用新式轉(zhuǎn)型方式:1)代碼容易辨識(shí);2)各轉(zhuǎn)型動(dòng)作的目標(biāo)比較窄,編譯器容易診斷;
? ? ? ? ? 轉(zhuǎn)型并不是告訴編譯器把某種類型視為另一種類型,任何一個(gè)類型轉(zhuǎn)換(不論是通過轉(zhuǎn)型操作而進(jìn)行的顯式轉(zhuǎn)換或通過編譯器完成的隱式轉(zhuǎn)換)往往真的令編譯器編譯出運(yùn)行期間執(zhí)行的代碼;
? ? ? ? ? 對(duì)于一個(gè)base class指針指向一個(gè)derived class對(duì)象,有時(shí)候上述的兩個(gè)指針值并不相同,它們之間會(huì)有一個(gè)偏移量在運(yùn)行期間施加在derived*指針上來得到正確的base*指針值;轉(zhuǎn)型代碼錯(cuò)誤分析:
class window{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?class specialwindow:public window{
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?public:
? ? ? ? ? ?virtual void onresize( ){...}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? virtual void onresize( ){
}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?static_cast<window>(*this).onresize( );//解決方法
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//window::onresize();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}
原本想著采用static_cast將派生類對(duì)象轉(zhuǎn)化為基類對(duì)象執(zhí)行onresize函數(shù),然后再執(zhí)行派生類的onresize函數(shù);但實(shí)際上是在當(dāng)前對(duì)象的副本上執(zhí)行了window::onresize,在當(dāng)前對(duì)象上執(zhí)行是specialwindow專屬動(dòng)作(個(gè)人理解:相當(dāng)于轉(zhuǎn)型可以當(dāng)做為創(chuàng)建一個(gè)副本執(zhí)行了一次函數(shù)調(diào)用的過程);
對(duì)于dynamic_cast:用于你相對(duì)一個(gè)derived class執(zhí)行derived class操作函數(shù),但是你的手上只有一個(gè)指向base的pointer或者reference,你只能通過dynamic_cast向下尋找你的derived class對(duì)象,然而使用dynamic_cast會(huì)用到strcmp比較class名稱,使得運(yùn)行效率極低;改善這個(gè)問題的方法有兩種:
? ? ? ? ? 1)使用容器并在其中存儲(chǔ)直接指向derived class對(duì)象指針(通常是智能指針),如此便消除了通過base class接口處理對(duì)象的需要(但這種情況下,你需要多個(gè)容器分別儲(chǔ)存不同派生類的指針,而且每個(gè)容器都具備類型安全性),具體代碼如下:
class window {...};
class specialwindow::public window{
public:
? ? ? ? ? ? ? ?void blink( );
};
typedef std::vector<std::trl:shared_ptr<specialwindow>vpsw;
vpsw winptrs;
for(vpsw::iterator iter=winptrs.begin( );iter!=winptrs.end( );iter++)
? ? ? ? ? ? ? ? ? (*it)->blink( );
? ? ? ? ? ? 2)通過base class接口處理所有可能之各種window派生類,也就是提供virtual函數(shù);
條款28:避免返回handles指向?qū)ο髢?nèi)部成分
? ? ? ? ? ?對(duì)于常見的pimpl設(shè)計(jì)方法(即數(shù)據(jù)和實(shí)現(xiàn)分離),有兩個(gè)需要注意的地方:1)成員變量的封裝性最多等于返回你reference的函數(shù)的訪問級(jí)別;2)const成員函數(shù)傳出來一個(gè)reference,后者(reference)所指數(shù)據(jù)與對(duì)象自身有關(guān)聯(lián),而它又被儲(chǔ)存于對(duì)象之外,那么函數(shù)調(diào)用者可以修改那筆數(shù)據(jù);
? ? ? ? ? ?絕對(duì)不要令成員函數(shù)返回一個(gè)指針指向訪問級(jí)別較低的成員函數(shù),如果你那么做,后者的訪問級(jí)別就會(huì)被提高到較高者,因?yàn)榭蛻艨梢匀サ囊粋€(gè)指向訪問級(jí)別更低的函數(shù),然后通過那個(gè)指針去調(diào)用它;
? ? ? ? ? ?如果有一個(gè)handle(成員函數(shù)返回reference,指針或者迭代器)被傳出去,那么就可以用這個(gè)handle訪問對(duì)象的數(shù)據(jù),對(duì)象的封裝性也就下降了;
? ? ? ? ?避免返回handles(reference,指針,迭代器)指向?qū)ο髢?nèi)部,遵守這個(gè)規(guī)定,可以增加封裝性,幫助const成員函數(shù)更像const,并將發(fā)生“虛吊號(hào)碼牌(指針或引用指向不存在的對(duì)象)”的可能性降到最低;
條款29:為“異常安全”而努力是值得的
? ? ? ? ?當(dāng)異常拋出時(shí),帶有異常安全性的函數(shù)會(huì):1)不泄露資源;2)不允許數(shù)據(jù)敗壞;
? ? ? ? ?異常安全函數(shù)提供一下三個(gè)保證中的一個(gè):1)基本承諾;異常被拋出,程序內(nèi)的任何事情仍然保持在有效的狀態(tài)下,沒有任何對(duì)象或者數(shù)據(jù)結(jié)構(gòu)會(huì)因此而敗壞,所有對(duì)象都處于一種內(nèi)部前后一致的狀態(tài);2)強(qiáng)烈保證:如果異常被拋出,程序狀態(tài)不改變。調(diào)用這樣的函數(shù)需有這樣的認(rèn)知,如果函數(shù)成功,就是完全成功,如果函數(shù)失敗,程序會(huì)回復(fù)到“調(diào)用函數(shù)之前的狀態(tài)”,調(diào)用一個(gè)提供強(qiáng)烈保證的函數(shù)后,程序狀態(tài)只有兩種可能,如預(yù)期般到達(dá)函數(shù)成功執(zhí)行后的狀態(tài)或者回到函數(shù)被調(diào)用前的狀態(tài)。3)不拋擲保證,承諾不拋擲異常,因?yàn)樗鼈兛偰芡瓿伤鼈冊(cè)瘸兄Z的功能;
? ? ? ? ?強(qiáng)烈保證往往能夠以copy-and-swap實(shí)現(xiàn)出來,但是強(qiáng)烈保證并非對(duì)所有的函數(shù)都有實(shí)現(xiàn)或者具備現(xiàn)實(shí)意義,一般基本承諾就可以了;
? ? ? ?函數(shù)提供的“異常安全性”通常最高只等于其所調(diào)用之各個(gè)函數(shù)的“異常安全性保證”中的最弱者。
條款31:透徹了解inlining的里里外外
? ? ? ? ? ?inlining函數(shù)的好處:產(chǎn)生較小的目標(biāo)碼,調(diào)用它們不需要承受函數(shù)調(diào)用所招致的額外開銷;缺點(diǎn)是會(huì)導(dǎo)致程序體積過大(虛內(nèi)存現(xiàn)象)inline造成代碼膨脹導(dǎo)致額外的換頁(yè)行為,降低高速緩存器裝置的擊中率;?
? ? ? ? ?inline只是對(duì)編譯器的一個(gè)申請(qǐng),不是強(qiáng)制命令,編譯器可以忽略;這項(xiàng)申請(qǐng)可以隱喻提出,也可以明確指出(加inline關(guān)鍵字)。隱喻的方式是將函數(shù)定義在class定義式內(nèi);
? ? ? ? inline函數(shù)通常被放置在頭文件中,在編譯階段實(shí)現(xiàn)對(duì)函數(shù)本體的替換;編譯器拒絕將復(fù)雜(循環(huán)或者遞歸)、virtual函數(shù)函數(shù)指針調(diào)用的情況下將函數(shù)變成inline;
? ? ? ? inline函數(shù)在隨著程序庫(kù)的升級(jí)時(shí)無法升級(jí),同時(shí)不支持設(shè)置端點(diǎn)調(diào)試(本身不是函數(shù));
? ? ? ?結(jié)論:將大多數(shù)inline函數(shù)限制在小型、被頻繁調(diào)用的函數(shù)身上,這可以使得日后調(diào)試過程和二進(jìn)制升級(jí)更容易,也可以使得潛在的代碼膨脹最小化,使程序的速度提升最大化;
? ? ? 不要只因?yàn)閒unction templates出現(xiàn)在頭文件,將它們聲明為inline;
條款31:將文件間的編譯依存關(guān)系降至最低
? ? ? ? 在設(shè)計(jì)對(duì)象的過程中,我們可以將對(duì)象分割為兩個(gè)class,一個(gè)只提供接口(數(shù)據(jù)),一個(gè)負(fù)責(zé)實(shí)現(xiàn)該接口(函數(shù)實(shí)現(xiàn)),這就是所謂的pimpl方式;這種設(shè)計(jì)方式的好處是:1)在修改數(shù)據(jù)代碼時(shí),我們只需要修改接口數(shù)據(jù)就行,而不要修改實(shí)現(xiàn);2)在函數(shù)聲明時(shí),當(dāng)其中有class時(shí),我們只需要聲明class而不需要定義class;具體設(shè)計(jì)策略如下:
? ? ? ? ? ? ? 如果使用object reference或者object pointers可以完成任務(wù),就不要使用objects;
? ? ? ? ? ? ? 如果能夠,盡量以class聲明式替換class定義式,這樣可以省去調(diào)用構(gòu)造函數(shù)的開銷;
? ? ? ? ? ? ?為聲明式和定義式提供不同的頭文件;
? ?上述的方法稱為handle class 的構(gòu)造方法,常見的handle class的構(gòu)造方法除了有pimpl方式,還有一種特殊的抽象基類(interface class),這種class的目的是調(diào)試derived class 接口,因此通常不帶成員變量,沒有構(gòu)造函數(shù),只有一個(gè)虛析構(gòu)函數(shù)和一組pure virtual函數(shù),如:
class person{
public:
? ? ? ? ? ?virtual ~person( );
? ? ? ? ? ?virtual std::string name( ) const=0;
? ? ? ? ? ?virtual std::string birthdata( ) const=0;
? ? ? ? ? virtual std::string address( )const=0;
};
interface class自己創(chuàng)建單個(gè)對(duì)象,這樣的函數(shù)稱為factory函數(shù)或virtual構(gòu)造函數(shù),它們返回指針(智能指針),指向動(dòng)態(tài)分配所得對(duì)象,而該對(duì)象支持interface class接口,通常函數(shù)被聲明為static;代碼如下:
class person{
public:
? ? ? ? ?static std::trl::shared_ptr<person>(create(const std::string&name,const Date&birthday,const address&addr);
};
? ? ? ? ? ?Handle class和interface class解除了接口和實(shí)現(xiàn)之間的耦合關(guān)系,從而降低文件間的編譯依存性;對(duì)于handle class,成員函數(shù)必須通過impl pointer取得對(duì)象數(shù)據(jù),每一次訪問就會(huì)增加一層間接性,同時(shí)每一個(gè)對(duì)象消耗的內(nèi)存數(shù)量必須增加impl pointer的大小,最后impl pointer必須初始化,指向一個(gè)動(dòng)態(tài)分配的impl object,因此可能會(huì)帶來bad alloc異常;interface class每次調(diào)用只付出一個(gè)間接跳躍成本,此外派生的interface class中必須含有一個(gè)vptr,這個(gè)指針會(huì)增加存放對(duì)象所需的內(nèi)存數(shù)量。
? ? ? ? 在程序開發(fā)中,如果使用handle class和interface class的實(shí)現(xiàn)碼在速度或大小差異大于類之間的耦合時(shí),可以用具體類代替handle class和interface class;
總結(jié)
以上是生活随笔為你收集整理的Effective C++学习第八天的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成都大熊猫繁育研究基地做地铁几号线
- 下一篇: Effective C++学习第九天