### 1 .讓自己習慣C++ #### 條款01 視C++為一個語言聯邦 * `C` * `Object-Oriented C++` * `Template C++` * `STL` * `C++`高效編程守則視情況而變化,取決於你使用`C++`的哪一部分。 #### 條款02 儘量與const, ...
1 .讓自己習慣C++
條款01 視C++為一個語言聯邦
C
Object-Oriented C++
Template C++
STL
C++
高效編程守則視情況而變化,取決於你使用C++
的哪一部分。
條款02 儘量與const,enum,inline替換#define
- 對於單純常量,最好以
const
對象或enums
替換#defines
。 - 對於形似函數的巨集(
macros
),最好改用inline
函數替換#defines
。
條款03 儘可能使用const
- 將某些東西聲明為
const
可以幫助編譯器偵測出錯誤用法。const
可被施加於任何作用域內的對象、函數參數、函數返回類型、成員函數本體。 - 編譯器強制實施
bitwise constness
,但你編寫程式時應該使用“概念上的常量性”(conceptual constness
)。 - 當
const
和non-const
成員函數有著實質等價的實現時,令con-const
版本調用const
版本可避免代碼重覆。
條款04 確定對象使用前已被初始化
- 為內置類型對象進行手工初始化,因為
C++
不保證初始化他們。 - 構造函數最好使用成員初始值列(
member initialization list
),而不要在構造函數本體內使用賦值操作(assignment
)。初始值列列出的成員變數,其排列次序應該和它們在class
中聲明的次序相同。 - 為免除“跨編譯單元之初始化次序”問題,請以
local static
對象替換non-local static
對象。
2. 構造/析構/賦值運算
條款05 瞭解C++預設編寫並調用哪些函數
- 編譯器可以暗自為
class
創建default
構造函數、copy
構造函數和copy assignment
操作符,以及析構函數。(C++11
開始還有move constructor
和move assignment
)。
條款06 若不想使用編譯器自動生成的函數,就應該明確拒絕
- 為駁回編譯器自動(暗自)提供的機能,可將相應的成員函數聲明為
private
並且不予實現。使用像uncopyable
這樣的base calss
也是一種做法。(C++11
以後可以使用=delete
告訴編譯器刪除不需要的成員函數。)
條款07 為多態積累聲明virtual析構函數
- 多態性質的
base calsses
應該聲明一個virtual
析構函數。如果class
帶有任何virtual
函數,它就應該擁有一個virtual
析構函數。 Classes
的設計目的如果不是作為base classes
使用,或不是為了具備多態(polymorphically
),就不應該聲明virtual
析構函數。
條款08 別讓異常逃離析構函數
- 析構函數絕對不要吐出異常。如果一個被析構哈你數調用的函數可能拋出異常,析構函數應該捕捉人分和異常,併吞下它們或結束程式。
- 如果客戶需要對某個操作函數運行期間拋出的異常做出反應,那麼
class
應該提供一個普通函數(而非析構函數中)執行該操作。
條款09 絕不在構造和析構過程中調用virtual函數
- 在構造和析構期間不要調用
virtual
函數,因為這類調用從不下降至derived class
。
條款10 令operator=返回一個reference to *this
- 為了實現“連鎖賦值”,應該令
operator=
返回一個reference to *this
。
條款11 在operator=中處理“自我賦值”
- 確保當對象自我賦值時
operator=
有良好的行為。其中技術包括比較“來源對象”和“目標對象”的地址、精心周到的語句順序、以及copy-and-swap
。 - 確定任何函數如果操作一個以上的對象,而其中多個對象時同一個對象時,其行為仍然正確。
條款12 複製對象時勿忘其每一個成分
Copying
函數應該確保賦值“對象內的所有成員變數”及“所有base class”成分。- 不要嘗試以某個
copying
函數實現另一個copying
函數。應該將共同機能放在第三個函數中,並有兩個copying
函數共同調用。
3. 資源管理
條款13 以對象管理資源
- 為了防止資源泄露,請使用
RAII
對象,它們在構造函數中獲得資源併在析構函數中釋放資源。 - 兩個常被使用的
RAII Class
分別時tr1::shared_ptr
和auto_ptr
。前者通常是較好的選擇,因為其copy
行為比較直觀。若選擇auto_ptr
,賦值動作會使它(被覆制物)指向null。(C++11
中使用std::shared_ptr
、std::unique_ptr
和std::weak_ptr
代替了兩者。)
條款14 在資源管理類中小心copying行為
- 複製
RAII
對象必須一併複製它所管理的資源,所以資源的copying
行為決定RAII
對象的copying
行為。 - 普通常見的
RAII class copying
行為是:抑制copying
、實行引用計數法(reference counting
)。不過其行為也都可以被實現。
條款15 在資源管理類中提供對原始資源的訪問
APIs
往往要求訪問原始資源(raw resources
),所以每一個RAII Class
應該提供一個“取得其所管理之資源”的辦法。- 對原始資源的訪問可能經由顯式轉換或隱式轉換。一般而言顯式轉換比較安全,隱式轉換對客戶比較方便。
條款16 成對使用new和delete時要採取相同形式
- 如果在
new
表達式中使用[]
,必須在相應的delete
表達式中也使用[]
。如如果在new
表達式中不使用[]
,一定不要在相應的delete
表達式中也使用[]
。
條款17 以獨立語句蔣newed對象置入智能指針
- 以獨立語句將
newed
對象存儲於(置入)智能指針內。如果不這樣做,一旦異常被拋出,有可能導致難以察覺的資源泄露。
4. 設計與聲明
條款18 讓介面容易被正確使用,不易被吳用
- 好的介面很容易被正確使用,不容易被誤用。應該在所有的介面中努力達成這些性質。
- “促進正確使用”的辦法包括介面的一致性,以及與內置類型的行為相容。
- “阻止誤用”的辦法包括建立新類型、限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任。
條款19 設計class猶如設計type
Class
的設計就是type
的設計。應該帶著和“語言設計者當初設計語言內置類型”時一樣的謹慎來研討class
的設計。
條款20 寧以pass-by-reference-to-const 替換 pass-by-value
- 儘量以
pass-by-reference-to-const
替換pass-by-value
。前者通常比較高效,並可避免切割問題。(slicing problem
) - 以上規則並不適用於內置類型,以及
STL
的迭代器和函數對象。對它們而言,pass-by-value
往往比較適當。
條款21 必須返回對象時,別妄想返回其reference
- 絕不要返回
pointer
或reference
指向一個local stack
對象,或返回reference
指向一個heap-allocated
對象,或返回pointer
或reference
指向一個local static
對象而有可能同事需要多個這樣的對象。
條款22 將成員變數聲明為private
- 切記將成員變數聲明為
private
。這可賦予客戶訪問數據的一致性、可細微劃分訪問控制、允諾約束條件獲得保證,並提供class
作者以充分的實現彈性。 protected
並不比public
更具封裝性。
條款23 寧以non-member、non-friend替換number函數
- 寧可拿
non-member non-friend
函數替換member
函數。這樣做可以增加封裝性、包裹彈性(packing flexibility
)和機能擴充性。
條款24 若所有參數皆需類型轉換,請為此採用non-number函數
- 如果需要為某個函數的所有參數(包括被
this
指針所指向的那個隱喻參數)進行類型轉換,那麼這個函數必須是個non-member
。
條款25 考慮寫出一個不拋棄異常的swap函數
- 當
std::swap
對你的類型效率不高時,提供一個swap
成員函數,並確定這個函數不拋出異常。 - 如果你提供一個
member swap
,也該提供一個non-member swap
用來調用前者。對於class
(而非template
),也請特化std;;swap
。 - 調用
swap
時應針對std::swap
使用using
聲明式,然後調用swap
並且不帶任何“命名空間資格修飾”。 - 為“用戶定義類型”進行
std templates
全特化時好的,但千萬不要嘗試在std
內加入某些對std
而言全新的東西。
5. 實現
條款26 儘可能延後變數定義式的出現時間
- 儘可能延後變數定義式的出現。這樣做可增加程式的清晰度並改善程式效率。
條款27 儘量少做轉型動作
- 如果可以,儘量避免轉型,特別時在註重效率的代碼中避免
dynamic_casts
。如果有個設計需要轉型動作,試著發展無需轉型的替代設計。 - 如果轉型時必須的,試著將它隱藏於某個函數背後。客戶隨後可以調用該函數,而不需將轉型放進自己的代碼內。
- 寧可使用
C++-style
(新式)轉型,而不是用舊式轉型。前者很容易辨識出來,而且也比較有著分門別類的執掌。
條款28 避免返回handles指向對象內部成員
- 避免返回
handles
(包括references
、指針、迭代器)指向內部對象。遵守這個條款可增加封裝性,幫助const
成員函數的行為像個const
,並將發生dangling handlers
的可能性降至最低。
條款29 為“異常安全”而努力是值得的
- 異常安全函數(
Exception-salf functions
)即使發生異常也不會泄露資源或允許任何數據結構破壞。這樣的函數區分為三種可能的保證:基本型、強烈型、不拋異常型。 - “強烈保證”往往能夠以
copy-and-swap
實現出來,但“強烈保證”並非對所有函數都可以實現或具備實現意義。 - 函數提供的“異常安全保證”通常最高只等於其所調用之各個函數的“異常安全保證”中的最弱者。
條款30 透徹瞭解inlining的裡裡外外
- 將大多數
inlining
限制在小型、被頻繁調用的函數身上。這可使日後的調試過程和二進位升級(binary upgradability
)更容易,也可使潛在的代碼膨脹問題最小化,使程式的速度提升機會最大化。 - 不要只因為
function templates
出現在頭文件,就將它們聲明為inline
。
條款31 將文件間的編譯依存關係降至最低
- 支持“編譯依賴最小化”的一般構想是:依賴於聲明式,不要依賴於定義式。基於此構想的兩個手段時
Handle classes
和Interface classes
。 - 程式庫頭文件應該以“完全且僅有聲明式”(
full and declaration-only forms
)的形式存在。這種做法不論是否設計templates
都適用。
6. 繼承與面向對象
條款32 確定你的public繼承塑模出Is-a關係
- “
public
繼承”意味Is-a
。適用於base class
身上的每一件事情一定也適用於derived classes
身上,因為每一個“derived class”對象也是一個base class
對象。
條款33 避免遮掩繼承而來的名稱
derived class
內的名稱會遮掩base class
內的名稱。在public
繼承下從來沒有人希望如此。- 為了讓被遮掩的名稱再見天日,可使用
using
聲明式或轉交函數(forwarding functions
)。
條款34 區分介面繼承和實現繼承
- 介面繼承和實現繼承不同。在
public
繼承之下,derived classes
總是繼承base class
的介面。 - 純虛(
pure virtual
)函數只具體指定介面繼承。 - 非純虛(
impure virtual
)函數具體指定介面繼承及預設實現繼承。 non-virtual
函數具體指定介面繼承預計強制性實現繼承。
條款35 考慮virtual函數以外的其他選擇
virtual
函數的替代方案包括NVI
手法及Strategy
設計模式的多種形式。NVI
手法自身時一個特殊形式的Template Method
設計模式。- 將機能從成員函數移到
class
外部函數,帶來的一個缺點時,非成員函數無法訪問class
的non-public
成員。 tr1::function
(C++11
已經移到std::function
)對象的行為就像一般函數指針。這樣的對象可接納“與給定之目標簽名式(target signature
)相容”的所有可調用物(callable entities
)。
條款36 絕不重新定義繼承而來的non-virtual函數
- 絕對不要重新定義繼承而來的
non-virtual
函數。
條款37 絕不重新定義繼承而來的預設參數
- 絕對不要重新定義一個繼承而來的預設參數值,因為預設參數值是靜態綁定,而
virtual
函數——你唯一應該覆寫的定西——確是動態綁定。
條款38 通過複合塑模出has-a或“根據某物實現出”
- 複合(
composition
)的意義和public
繼承完全不同。 - 在應用域(
application domain
),複合意味著has-a
(有一個),在實現域(implementation domain
),複合意味著is-implemented-in-terms-of
(根據某物實現出)。
條款39 明智而審慎的使用private繼承
Private
繼承意味著is-implemented-in-terms-of
(根據某物實現出)。它通常比複合(composition
)的級別低。但是當derived class
需要訪問protected base class
的成員,或需要重新定義繼承而來的virtual
函數時,這麼設計時合理的。- 和複合(
composition
)不同,private
繼承可以造成empty base
最優化。這對致力於“對象尺寸最小化”的程式庫開發者而言,可能很重要。
條款40 明智而審慎的使用多重繼承
- 多重繼承比單一繼承複雜。它可能導致新的歧義性,以及對
virtual
繼承的需要。 virtual
繼承會增加大小、速度、初始化(及賦值)複雜度等成本。如果virtual base classes
不帶任何數據,將時最具有實用價值的情況。- 多重繼承的確有正當用途。其中一個情節涉及“
public
繼承某個interface class
”和“private
繼承某個協助實現的class
”的兩相組合。
7. 模板與泛型編程
條款41 瞭解隱式介面和編譯器多態
classes
和templates
都支持介面(interface
)接多態(polymorphism
)。- 對
classes
而言介面時顯式的(explicit
),以函數簽名為中心,多態則是通過virtual
函數發生於運行期。 - 對於
templates
參數而言,介面是隱式的(implicit
),奠基於有效表達式。多態則時通過template
具現化和函數重載 解析(function overloading resolution
)發生於編譯期。
條款42 瞭解typename的雙重意義
- 聲明
template
參數時,首碼關鍵字class
和typename
可互換。 - 請使用關鍵字
typename
標識嵌套從屬類型名稱;但不得在base class lists
(基類列)或member initialization list
(成員初始值列)內以它最為base class
修飾符。
條款43 學習處理模板化基類內的名稱
- 可在
derived class templates
內通過this->
指涉base class templates
內的成員名稱,或由一個明白寫出的“base class
資格修飾符”完成。
條款44 將與參數無關的代碼抽離templates
Templates
生成多個classes
和多個函數,所有任何template
代碼都不該與某個造成膨脹的template
參數產生相依關係。- 因非類型模板參數(
non-type template parameters
)而造成的代碼膨脹往往可以消除,做法是以函數參數或class
成員變數替換template
參數。
條款45 運用成員函數末班接受所有相容類型
- 請使用
member functions templates
(成員函數模板)生成“可接受所有相容類型”的函數。 - 如果你聲明
member templates
用於“泛化copy
構造”或“泛化assignment
操作”,你還是需要聲明正常的copy
構造函數和copy assignment
操作符。
條款46 需要類型轉換時請為模板定義非成員函數
- 當編寫一個
class template
,而它所提供的“與此template
相關的”函數支持“所有參數之隱式類型轉換”時,請將那些函數定義為“class template
內部的friend
函數”。
條款47 請使用traits classes表現類型信息
Traits classes
使得“類型相關信息”在編譯期可用。它們以template
和"template
特化"完成實現。- 整合重載技術(
overloading
)後,traits calsses
有可能在編譯器對類型執行if...else
測試。
條款48 認識template元編程
Template metaprogramming
(TPM
,模板元編程)可將工作由運行期移到編譯期,因而得以實現早期錯誤偵測和更高的執行效率。TMP
可被用來生成“基於政策選擇組合”(based on combinations of policy choices
)的客戶定製代碼,也可用來避免生成對某些特殊類型並不適合的代碼。
8. 定製new和delete
條款49 瞭解new-handler的行為
set_new_handler
允許客戶指定一個函數,在記憶體分配無法獲得滿足時被調用。Nothrow new
是一個頗為局限的工具,因為它只適用於記憶體分配,後繼的構造函數調用還是可能拋出異常。
條款50 瞭解new和delete的合理替換時機
- 有許多理由需要寫個自定義的
new
和delete
,包括改善效能、對heap
運用錯誤進行調試、收集heap
使用信息。
條款51 編寫new和delete時需固守常規
operator new
應該內含一個無窮迴圈,併在其中嘗試分配記憶體,如果它無法滿足記憶體需求,就該調用new-handler
。它也應該有能力處理0 byte
申請。class
專屬版本則還應該處理“比正確大小更大的(錯誤)申請”。operator delete
應該在收到null
指針時不做任何事。class
專屬版本則還應該處理“比正確大小更大的(錯誤)申請”。
條款52 寫了placement new也要寫placement delete
- 當你寫一個
placement operator new
,請確定也寫出對應的placement operator delete
。如果沒有這樣做,你的程式可能會發生隱微而時斷時續的記憶體泄露。 - 當你聲明
placement new
和placement delete時,確定不要無意識(非故意)的掩蓋了它們的正常版本。
9. 雜項討論
條款53 不要輕忽編譯器的警告
- 請嚴肅對待編譯器發出的警告信息。努力在你的編譯器的最高(最嚴苛)警告級別下爭取“無任何警告”的榮譽。
- 不要過度依賴編譯器的報警能力,因為不同的編譯器對待事情的態度並不相同。一旦移植到另一個編譯器上,你原本依賴的警告信息有可能消失。
條款54 讓自己熟悉包括TR1在內的標準程式庫
C++
標準庫的主要機能由STL
、iostreams
、locales
組成。並包含C99
標準程式庫。TR1
添加了智能指針(例如tr1::shared_ptr
)、一般化函數指針(tr1::function
)、hash-based
容器(unorderd_map,unordered_set
)、正則表達式(regular expressions
)以及另外10個組件的支持。TR1
自身只是一份規範。為了獲得TR1
提供的好處,你需要一份實物。一個好的實物來源時Boost
。
條款55 讓自己熟悉Boost
Boost
是一個社群,也是一個網站。致力於免費、源碼開放、同僚覆審的C++
程式庫開發。Boost
在C++
標準化過程中扮演深具影響力的角色。Boost
提供許多TR1
組件的實現品,以及其他許多程式庫
關註我,帶你21天“精通”C++!(狗頭)