天天看點

Effective C++第三版之(五)Implementations

Implementations實作

條款26:Postpone variable definitions as long as possible.盡可能延後變量定義式的出現

1,自定義變量類型有構造函數和析構函數,程式控制流到達定義式有對象構造成本,變量離開其作用域時有對象析構成本,即使變量未使用,應盡可能避免此類情況。

2,定義了變量而程式提前離開,變量未使用,仍有構造和析構成本,在确實需要使用變量前定義變量。

條款27:Minimize casting.盡可能少的類型轉換

1,C++提供四種類型轉換,轉換傳回對象轉換後的副本。

1)const_cast通常用于移除對象的常量特性,也是惟一有此能力的C++類型轉換。

2)dynamic_cast主要用于繼承體系中向下安全的類型轉換,基類指針或引用(指向派生類對象)轉換為派生類指針或引用,調用派生類非虛函數。

3)reinterpret_cast用于執行低級轉換,實際取決于編繹器。

4)static_cast用于強制的隐式類型轉換,例如将non-const對象轉換為const對象,将int轉換為double,void*轉換為typed指針,基類指針或引用轉換為派生類指針或引用,将explicit的構造函數顯式的執行從構造函數參數類型到類類型的強制轉換。

2,無論是顯式的類型轉換還是編繹器完成的隐式類型轉換,都會産生代碼。

3,dynamic_cast可能耗費較大的運作成本,執行速度較慢。

1)至少需要比較class名稱,如果繼承體系較深,需要多次比較class名稱,深度繼承和多重繼承成本較高。

2)避免此類設計可将非虛函數設計為虛函數,基類指針實作派生類虛函數的調用。

4,盡量避免類型轉換,特别是dynamic_cast的轉換,必要的類型轉換盡量隐藏于函數内部使用以上方式執行類型轉換,避免調用者實作類型轉換。

條款28:Avoid returning “handles” to object internals.避免傳回handles指向對象内部成員。

1,引用、指針、疊代器統稱為handles。

1)傳回對象内部私有成員的handles,降低對象封裝性。

2)傳回對象内部成員的handles,如果對象析構,内部成員不存在,則造成空懸handles。

3)const成員函數傳回handles,對象狀态可被更改,傳回const引用,可避免對象更改。

2,容器中operator[]允許傳回handles。

條款29:Strive for exception-safe code.為異常安全而努力

1,當異常被抛出,帶有異常安全性的函數必須不洩漏任何資源,不破壞資料。

2,異常安全函數須保證:

1)基本保證,異常被抛出,程式内部處于前後一緻的任何有效狀态;

2)強烈保證,異常被抛出,程式狀态恢複到調用之前狀态,狀态未發生改變;

3)不抛出保證,承諾完成函數功能絕不抛出異常,内置類型所有操作都提供了nothrow保證。

3,從異常安全性出發,nothrow函數很好,但在C part of C++領域很難做到不抛出異常,例動态記憶體配置設定記憶體不足時通常抛出bad_alloc異常,是以大部分函數僅能選擇基本保證和強烈保證。

4,強烈保證的一般化設計政策為copy and swap,在副本做一切修改,若抛出異常原對象未改變狀态,等修改成功後将修改後的副本與原對象在一個不抛出異常的操作中轉換。

5,當強烈保證不切實際或效率及複雜度成本較高時,可選擇基本保證。

6,函數提供的的異常安全保證通常最高隻等于所調用各函數異常安全中的最弱者。

條款30:Understand the ins and outs of inlining.透徹了解inlining的裡裡外外

1,inline函數小型且頻繁調用,動作像函數,優于宏,不需要承擔函數調用的開銷,可解決函數頻繁調用大量消耗棧記憶體的問題,但會造成代碼膨脹程式體積過大而導緻額外的換頁行為,降低指令在高速緩存的命中。

2,inline函數通常置于頭檔案中,編繹器在編繹過程中将inline函數調用替換為函數代碼,但不是強制指令。

1)編繹器拒絕将過複雜例如帶有循環或遞歸的函數inline替換。

2)拒絕對virtual函數inline替換,因虛函數在運作期才能确定調用哪個函數,而inline在編繹期向編繹器申請執行代碼替換。

3,顯式定義inline函數在聲明式前加關鍵字inline。

1)定義于class内的函數隐式聲明為inline。

2)templates定義于頭檔案内,但不一定是inline函數,需顯式聲明為inline。

4,通過函數指針調用inline函數不執行代碼替換,函數指針指向一個inline函數,編繹器會生成一個outline副本,将指針指向該副本位址。

5,當使用new或delete時,對象的構造函數和析構函數自動調用,當建立一個對象,所有base class和成員變量自動構造,當對象構造抛出異常,對象中已構造好的部分會自動銷毀,編繹器自動生成代碼,如果base class和成員變量的構造是inline,派生類構造函數中插入base class和成員變量的構造代碼,析構函數同樣如此。

6,inline函數設計時需要考慮到,inline函數的改變所有調用inline函數的程式必須重新編繹,如果是non-inline函數有修改,程式重新連接配接比重新編繹負擔較小。

條款31:Minimize compilation dependencies between files.降低檔案間編繹依賴關系

1,頭檔案中包含其他頭檔案存在編繹依賴關系,如果包含的頭檔案發生改變,存在依賴關系的頭檔案也需要重新編繹。

1)如果class内成員是自定義類型,自定義類型頭檔案發生改變或依賴的其他頭檔案發生改變,則class需要重新編繹,包含class的所有頭檔案都需要重新編繹。

2,為避免連串編繹依賴關系,将接口定義與實作分離。

1)一個隻提供接口,另一個負責接口的實作。

2)在接口定義類中定義指向接口實作類的指針,pimpl(pointer to implementation) idiom,在這種設計下實作類中包含檔案的内容發生修改實作類需要重新編繹,接口定義類中使用前置類型聲明不需要重新編繹,隻有在接口發生改變時本檔案需要重新編繹。

3,隻有類型聲明時可以定義指向該類型的引用和指針,函數聲明中形參和傳回值可以使用該類型,如果需要定義該類型的對象,則需要該類型的定義式,需要包含該類型定義的頭檔案。

1)使用前置類型聲明,函數聲明中可使用該類型的形參和傳回值,但調用函數前需要該類型的定義,此種形式可将類型頭檔案包含從函數聲明轉移到函數調用,降低編繹依賴,以類的聲明式替代定義式。

2)以聲明的依賴性替代定義的依賴性,可達到依賴最小化,可為聲明式和定義式提供不同頭檔案,聲明式中聲明不同類型,包含該聲明式檔案可在本檔案中省去前置類型聲明。

3)标準庫聲明式頭檔案<iosfwd>内含iostream各元件的聲明式,對應的定義式則分布在若幹不同的頭檔案内<sstream>,<streambuf>,<fstream>,<iostream>。

4,Interface classes以虛基類指針或引用指向派生類對象,虛基類中定義靜态工廠函數,根據輸入不同構造不同派生類對象傳回虛基類指針或引用。虛基類中定義虛接口函數,派生類實作具體定義。

5,Handle classes即pimpl(pointer to implementation) idiom和Interface classes即解除接口和實作之間的耦合,降低檔案的編繹依賴,會降低程式運作期速度以及增加部分記憶體。如果速度或記憶體大小差異過大classes之間耦合并不重要時可舍棄,以具象類代替。

1)在Handle classes成員函數需要通過implementation pointer擷取對象資料,為通路增加了一層間接性,也必然增加了implementation pointer的記憶體。

2)implementation pointer的初始化指向動态記憶體,帶來記憶體配置設定及釋放的額外開銷。

3)Interface classes則增加了虛函數表的記憶體。

繼續閱讀