條款26:盡可能延后變量定義式的出現時間
? ? ? ? ? 當你定義了一個變量,如果在使用變量之前出現異常,那么你得承受一次構造成本和析構成本,而且你沒有使用該變量;本條款給出的建議是延遲變量的定義,直到非得使用該變量的前一刻為止,甚至應該嘗試延后這份定義知道能夠給它初值實參為止;這樣不僅可以避免構造(析構)非必要的對象,還可以避免無意義的default構造行為;
條款27:盡量少做轉型動作
? ? ? ? ? 常見C風格的類型轉換:(T)expression? ? ? ? ? ? ? ? ?函數風格的轉型? ?T(expression)? ? ? //舊式轉型
? ? ? ? ? C++提供的四種新式轉型:
? ? ? ? ? ? ? ? const_cast<T>(expression);//去除對象常量性;
? ? ? ? ? ? ? ? dynamic_cast<T>(expression);//對象安全向下轉型,用于繼承
? ? ? ? ? ? ? ? reinterpret_cast<T>(expression);
? ? ? ? ? ? ? ? static_cast<T>(expression);//隱式轉型
盡量使用新式轉型方式:1)代碼容易辨識;2)各轉型動作的目標比較窄,編譯器容易診斷;
? ? ? ? ? 轉型并不是告訴編譯器把某種類型視為另一種類型,任何一個類型轉換(不論是通過轉型操作而進行的顯式轉換或通過編譯器完成的隱式轉換)往往真的令編譯器編譯出運行期間執行的代碼;
? ? ? ? ? 對于一個base class指針指向一個derived class對象,有時候上述的兩個指針值并不相同,它們之間會有一個偏移量在運行期間施加在derived*指針上來得到正確的base*指針值;轉型代碼錯誤分析:
class window{? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?class specialwindow:public window{
public:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?public:
? ? ? ? ? ?virtual void onresize( ){...}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? virtual void onresize( ){
}? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?static_cast<window>(*this).onresize( );//解決方法
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//window::onresize();
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?}
原本想著采用static_cast將派生類對象轉化為基類對象執行onresize函數,然后再執行派生類的onresize函數;但實際上是在當前對象的副本上執行了window::onresize,在當前對象上執行是specialwindow專屬動作(個人理解:相當于轉型可以當做為創建一個副本執行了一次函數調用的過程);
對于dynamic_cast:用于你相對一個derived class執行derived class操作函數,但是你的手上只有一個指向base的pointer或者reference,你只能通過dynamic_cast向下尋找你的derived class對象,然而使用dynamic_cast會用到strcmp比較class名稱,使得運行效率極低;改善這個問題的方法有兩種:
? ? ? ? ? 1)使用容器并在其中存儲直接指向derived class對象指針(通常是智能指針),如此便消除了通過base class接口處理對象的需要(但這種情況下,你需要多個容器分別儲存不同派生類的指針,而且每個容器都具備類型安全性),具體代碼如下:
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函數;
條款28:避免返回handles指向對象內部成分
? ? ? ? ? ?對于常見的pimpl設計方法(即數據和實現分離),有兩個需要注意的地方:1)成員變量的封裝性最多等于返回你reference的函數的訪問級別;2)const成員函數傳出來一個reference,后者(reference)所指數據與對象自身有關聯,而它又被儲存于對象之外,那么函數調用者可以修改那筆數據;
? ? ? ? ? ?絕對不要令成員函數返回一個指針指向訪問級別較低的成員函數,如果你那么做,后者的訪問級別就會被提高到較高者,因為客戶可以去的一個指向訪問級別更低的函數,然后通過那個指針去調用它;
? ? ? ? ? ?如果有一個handle(成員函數返回reference,指針或者迭代器)被傳出去,那么就可以用這個handle訪問對象的數據,對象的封裝性也就下降了;
? ? ? ? ?避免返回handles(reference,指針,迭代器)指向對象內部,遵守這個規定,可以增加封裝性,幫助const成員函數更像const,并將發生“虛吊號碼牌(指針或引用指向不存在的對象)”的可能性降到最低;
條款29:為“異常安全”而努力是值得的
? ? ? ? ?當異常拋出時,帶有異常安全性的函數會:1)不泄露資源;2)不允許數據敗壞;
? ? ? ? ?異常安全函數提供一下三個保證中的一個:1)基本承諾;異常被拋出,程序內的任何事情仍然保持在有效的狀態下,沒有任何對象或者數據結構會因此而敗壞,所有對象都處于一種內部前后一致的狀態;2)強烈保證:如果異常被拋出,程序狀態不改變。調用這樣的函數需有這樣的認知,如果函數成功,就是完全成功,如果函數失敗,程序會回復到“調用函數之前的狀態”,調用一個提供強烈保證的函數后,程序狀態只有兩種可能,如預期般到達函數成功執行后的狀態或者回到函數被調用前的狀態。3)不拋擲保證,承諾不拋擲異常,因為它們總能完成它們原先承諾的功能;
? ? ? ? ?強烈保證往往能夠以copy-and-swap實現出來,但是強烈保證并非對所有的函數都有實現或者具備現實意義,一般基本承諾就可以了;
? ? ? ?函數提供的“異常安全性”通常最高只等于其所調用之各個函數的“異常安全性保證”中的最弱者。
條款31:透徹了解inlining的里里外外
? ? ? ? ? ?inlining函數的好處:產生較小的目標碼,調用它們不需要承受函數調用所招致的額外開銷;缺點是會導致程序體積過大(虛內存現象)inline造成代碼膨脹導致額外的換頁行為,降低高速緩存器裝置的擊中率;?
? ? ? ? ?inline只是對編譯器的一個申請,不是強制命令,編譯器可以忽略;這項申請可以隱喻提出,也可以明確指出(加inline關鍵字)。隱喻的方式是將函數定義在class定義式內;
? ? ? ? inline函數通常被放置在頭文件中,在編譯階段實現對函數本體的替換;編譯器拒絕將復雜(循環或者遞歸)、virtual函數函數指針調用的情況下將函數變成inline;
? ? ? ? inline函數在隨著程序庫的升級時無法升級,同時不支持設置端點調試(本身不是函數);
? ? ? ?結論:將大多數inline函數限制在小型、被頻繁調用的函數身上,這可以使得日后調試過程和二進制升級更容易,也可以使得潛在的代碼膨脹最小化,使程序的速度提升最大化;
? ? ? 不要只因為function templates出現在頭文件,將它們聲明為inline;
條款31:將文件間的編譯依存關系降至最低
? ? ? ? 在設計對象的過程中,我們可以將對象分割為兩個class,一個只提供接口(數據),一個負責實現該接口(函數實現),這就是所謂的pimpl方式;這種設計方式的好處是:1)在修改數據代碼時,我們只需要修改接口數據就行,而不要修改實現;2)在函數聲明時,當其中有class時,我們只需要聲明class而不需要定義class;具體設計策略如下:
? ? ? ? ? ? ? 如果使用object reference或者object pointers可以完成任務,就不要使用objects;
? ? ? ? ? ? ? 如果能夠,盡量以class聲明式替換class定義式,這樣可以省去調用構造函數的開銷;
? ? ? ? ? ? ?為聲明式和定義式提供不同的頭文件;
? ?上述的方法稱為handle class 的構造方法,常見的handle class的構造方法除了有pimpl方式,還有一種特殊的抽象基類(interface class),這種class的目的是調試derived class 接口,因此通常不帶成員變量,沒有構造函數,只有一個虛析構函數和一組pure virtual函數,如:
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自己創建單個對象,這樣的函數稱為factory函數或virtual構造函數,它們返回指針(智能指針),指向動態分配所得對象,而該對象支持interface class接口,通常函數被聲明為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解除了接口和實現之間的耦合關系,從而降低文件間的編譯依存性;對于handle class,成員函數必須通過impl pointer取得對象數據,每一次訪問就會增加一層間接性,同時每一個對象消耗的內存數量必須增加impl pointer的大小,最后impl pointer必須初始化,指向一個動態分配的impl object,因此可能會帶來bad alloc異常;interface class每次調用只付出一個間接跳躍成本,此外派生的interface class中必須含有一個vptr,這個指針會增加存放對象所需的內存數量。
? ? ? ? 在程序開發中,如果使用handle class和interface class的實現碼在速度或大小差異大于類之間的耦合時,可以用具體類代替handle class和interface class;