天天看點

Effective C++筆記

Effective C++筆記 -- 改善程式與設計的55個具體做法

改善程式與設計的55個具體做法

1. 視C++為一個語言聯邦

  • C++并不是一個帶有一組守則的一體語言:它是從四個次語言(C, Object-Oriented C++, Template C++ 以及 STL)組成的聯邦政府,每個語言都有自己的規約。
  • C++的高效程式設計守則視狀況而變化,取決于你使用C++的哪一部分

2. 盡量以const, enum, inline替換#define

  • 即甯可以編譯器替換預處理器
  • 對于單純常量,最好以const對象或enums替換#define
  • 對于形似函數的宏,最好改用inline函數替換#define

3. 盡可能使用const

  • 将某些東西聲明為const可幫助編譯器偵測出錯誤用法。const可被施加于任何作用域内的對象、函數參數、函數傳回類型、成員函數本體
  • 編譯器強制實施bitwise constness,但你編寫程式時應該使用“概念上的常量性”
  • 當const和non-const成員函數有着實質等價的實作時,令non-const版本調用const版本可避免代碼重複

4.确定對象被使用前已被初始化

  • 為内置型對象進行手工初始化,因為C++不保證初始化它們
  • 構造函數最好使用成員初始值列,而不要在構造函數本體内使用指派操作。初值列列出的成員變量,其排列次序應該和它們在class中的聲明次序相同
  • 為免除“跨編譯單元之初始化次序”問題,請以local static對象替換non-local static對象

5. 了解C++默默編寫并調用了哪些函數

  • 編譯器可以暗自為class建立default構造函數、copy構造函數、copy assignment操作符,以及析構函數

6. 如不想使用編譯器自動生成的函數,就該明确拒絕

  • 為駁回編譯器自動提供的機能,可将相應的成員函數聲明為private并且不予實作

7. 為多态基類聲明virtual析構函數

  • 帶有多态性質的基類應該聲明一個virtual析構函數。如果class帶有任何virtual函數,它就應該擁有一個virtual析構函數
  • 類的設計如果不是作為基類使用,或不是為了具備多态性,就不該聲明virtual析構函數

8. 别讓異常逃離析構函數

  • 隻要析構函數突出異常,程式就可能過早結束或出現不明确行為
  • 析構函數絕對不要吐出異常。如果一個被析構函數調用的函數可能抛出異常,析構函數應該捕獲任何異常,然後吞下它們(不傳播)或者結束程式
  • 如果客戶需要對某個操作函數運作期間抛出的異常做出反應,那麼class應該提供一個普通函數(而非在析構函數中)執行該操作

9. 絕不在構造和析構過程中調用virtual函數

  • 在構造和析構期間不要調用virtual函數,因為這類調用從不下降至派生類

10. 令operator=傳回一個reference to *this

  • 為了實作連鎖指派

11. 在operator=中處理“自我指派”

  • 確定對象自我指派時operator=有良好行為。其中技術包括“來源對象”和“目标對象”的位址、精心周到的語句順序以及copy-and-swap
  • 确定任何函數如果操作一個以上對象,而其中多個對象是同一個對象時,其行為仍然正确

12. 複制對象時勿忘其每一個成分

  • Copying函數應該確定複制“對象内的所有成員變量”以及“所有base class成分”
  • 不要嘗試以某個copying函數實作另一個copying函數。應該将共同機能放進第三個函數中,并由兩個copying函數共同調用

13. 以對象管理資源

  • 獲得資源後立刻放進管理對象
  • 管理對象運用析構函數確定資源被釋放
  • 為防止資源洩露,請使用RAII對象,它們在構造函數中獲得資源并在析構函數中釋放資源
  • 兩個常被使用的RAII classes分别是tr1::shared_ptr和auto_ptr。前者通常是較佳選擇,因為其copy行為比較直覺。若選擇auto_ptr,複制動作會使它(被複制物)指向null

14. 在資源管理類中小心copying行為

  • 複制RAII對象必須一并複制它所管理的資源,是以資源的copying行為決定RAII對象的copying行為
  • 普遍而常見的RAII class copying行為是:抑制copying、施行引用計數法。不過其他行為也都可能被實作

15. 在資源管理類中提供對原始資源的通路

  • APIs往往要求通路原始資源,是以每一個RAII class應該提供一個“取得其所管理的資源”的辦法
  • 對原始資源的通路可能經由顯示轉換或隐式轉換。一般而言顯示轉換比較安全,但隐式轉換對客戶比較友善

16. 成對使用new和delete時,要采取相同形式

  • 如果你在new表達式中使用[],必須在相應的delete表達式中也使用[]。如果在new表達式中不使用,在delete中也不要使用

17. 以獨立語句将new對象置入智能指針

  • 如果不這樣做,一旦異常被抛出,有可能導緻難以察覺的資源洩露

18. 讓接口容易被正确使用,不易被誤用

  • 好的接口很容易被正确使用,不容易被誤用。你應該在你的所有接口中努力達成在這些性質
  • “促進正确使用”的辦法包括接口的一緻性,以及與内置類型的行為相容
  • “阻止誤用”的辦法包括建立新類型、限制類型上的操作,束縛對象值,以及消除客戶的資源管理責任
  • tr1::shared_ptr支援定制型删除器。這可防範DLL問題,可被用來自動解除互斥鎖等等

19. 設計class猶如設計type

20. 甯以pass-by-reference-to-const替換pass-by-value

  • 前者通常比較高效,并可避免切割問題
  • 該規則并不适用内置類型,以及STL的疊代器和函數對象。對它們而言,pass-by-value往往比較适當

21. 必須傳回對象時,别妄想傳回其reference

22. 将成員變量聲明為private

  • 這可賦予客戶通路資料的一緻性、可細微劃分通路控制,允諾限制條件獲得保證,并提供class作者以充分的實作彈性
  • protected并不比public更具有封裝性

23. 甯以non-member、non-friend替換member函數

  • 這樣做可以增加封裝性、包裹彈性和機能擴充性

24. 若所有參數皆需類型轉換,請為此采用non-member函數

  • 即,如果你需要為某個函數(包括被this指針所指的那個隐喻參數)進行類型轉換,那麼這個函數必須是個non-member

25. 考慮寫出一個不抛出異常的swap函數

  • 當std:swap對你的類型效率不高時,提供一個swap成員函數,并确定這個函數不抛出異常
  • 如果你提供一個member swap,也該提供一個non-member swap用來調用前者。對于classes,也請特化std:swap
  • 調用swap時應針對std::swap使用using聲明式,然後調用swap并且不帶任何“命名空間資格修飾”
  • 為“使用者定義類型”進行std templates全特化是好的,但千萬不要嘗試在std内加入某些對std而言全新的東西

26. 盡可能延後變量定義式的出現時間

  • 這樣可增加程式的清晰度并改善程式效率

27. 盡量少做轉型動作

  • const_cast通常被用來将對象的常量性剔除
  • dynamic_cast主要用來執行“安全向下轉型”,也就是用來判斷某對象是否歸屬繼承體系中的某個類型
  • reinterpret_cast意圖執行低級轉型,實際動作及結果可能取決于編譯器,這也就表示它不可移植
  • static_cast用來強迫隐式轉換,但它無法将const轉為non-const
  • 如果可以,盡量避免轉型,特别是在注重效率的代碼中避免dynamic_casts。如果有個設計需要轉型動作,試着發展無需轉型的替代設計
  • 如果轉型是必要的,試着将它隐藏于某個函數背後。客戶可以調用該函數,而不需要将轉型放進他們自己的代碼内
  • 甯可以C++-style轉型,不要使用舊式轉型

28. 避免傳回handles指向對象内部成分

  • 該條款可增加封裝性,幫助const成員函數的行為像個const,并将發生 dangling handles的可能性降至最低

29. 為“異常安全”而努力是值得的

  • 異常安全函數提供一下三個保證之一
  1. 基本承諾:如果異常被抛出,程式内的任何事物仍然保持在有效狀态下
  2. 強烈保證:如果異常被抛出,程式狀态不改變
  3. 不抛擲保證:承諾絕不抛出異常,因為它們總是能夠完成它們原先承諾的功能

30. 徹底了解inlining的裡裡外外

  • 将inlining限制在小型、被頻繁調用的函數身上
  • 不要隻因為function templates出現在頭檔案,就将它們聲明為inline

31. 将檔案間的編譯依存關系降至最低

  • 如果使用object references或object pointers可以完成任務,就不要使用objects
  • 如果能夠,盡量以class聲明式替換class定義式
  • 為聲明式和定義式提供不同的頭檔案
  • 程式庫頭檔案應該以“完全且僅有聲明式”的形式存在

32. 确定你的public繼承塑模出is-a關系

33. 避免遮掩繼承而來的名稱

  • 派生類内的名稱會遮掩基類内的名稱。在public繼承下從沒有人希望如此
  • 為了讓被遮掩的名稱再見天日,可使用using聲明式或轉交函數

34. 區分接口繼承和實作繼承

  • 接口繼承和實作繼承不同。在public繼承之下,派生類總是繼承基類的接口
  • pure virtual函數隻具體指定接口繼承
  • impure virtual函數具體指定接口繼承及預設實作繼承
  • non-virtual函數具體指定接口繼承以及強制性實作繼承

35. 考慮virtual函數以外的其他選擇

  • virtual函數的替代方案包括NVI手法以及Strategy設計模式的多種形式。NVI手法自身是一個特殊形式的Template Method設計模式
  • 将機能從成員函數轉移到class外部函數,帶來的一個缺點是,非成員函數無法通路class的non-public成員
  • tr1::function對象的行為就像一般函數指針。這樣的對象可接納“與給定的目标标簽格式相容”的所有可調用物

36. 絕不重新定義繼承而來的non-virtual函數

37. 絕不重新定義繼承而來的預設參數值

  • 因為預設數值都是靜态綁定,而virtual函數确實動态綁定

38. 通過複合塑模出has-a或“根據某物實作出”

39. 明智而審慎地使用private繼承

  • private繼承意味着is-implemented-in-terms-of(根據某物實作出)。它通常比複合的級别低。但是當派生類需要通路受保護的基類的成員,或需要重新定義繼承而來的virtual函數時,這麼設計是合理的
  • 和複合不同,private繼承可以造成empty base最優化。這對緻力于“對象尺寸最小化”的程式庫開發者而言,可能很重要

40. 明智而審慎地使用多重繼承

  • 多重繼承比單一繼承複雜。它可能導緻新的歧義性,以及對virtual繼承的需要
  • virtual繼承會增加大小、速度、初始化(及指派)複雜度等等成本。如果virtual base classes不帶任何資料,将是最具使用價值的情況
  • 多重繼承的确有正當用途。其中一個情節涉及“public繼承某個Interface class”和“private繼承某個協助實作的class”的兩相組合

41. 了解隐式接口和編譯器多态

  • class和templates都支援接口和多态
  • 對classes而言接口是顯示的,以函數簽名為中心,多态則是通過virtual函數發生于運作期
  • 對template參數而言,接口是隐式的,奠基于有效表達式。多态則是通過template具現化和函數重載解析發生于編譯期

42. 了解typename的雙重意義

  • 聲明template參數時,字首關鍵字class和typename可互換
  • 請使用關鍵字typename辨別嵌套從屬類型名稱;但不得在base class lists或member initialization list内以它作為base class修飾符

43. 學習處理模闆化基類内的名稱

44. 将與參數無關的代碼抽離templates

  • Templates生成多個classes和多個函數,是以任何template代碼都不該與某個造成膨脹的template參數産生相依關系
  • 因非類型模闆參數而造成的代碼膨脹,往往可消除,做法是以函數參數或class成員變量替換template參數
  • 因類型參數而造成的代碼膨脹,往往可降低,做法是讓帶有完全相同二進制表述的具現類型共享實作碼

45. 運用成員函數模闆接受所有相容類型

  • 如果你聲明member templates用于泛化copy構造或泛化assignment操作,你還需要聲明正常的copy構造函數和copy assignment操作符

46. 需要類型轉換時請為模闆定義非成員函數

  • 當我們編寫一個class template,而它所提供的“與此template相關的”函數支援“所有參數的隐式類型轉換”時,請将那些函數定義為“class template内部的friend函數”

47. 請使用traits classes表現類型資訊

  • Traits classes使得“類型相關資訊”在編譯器可用。它們以templates和“templates特化”完成實作
  • 整合重載技術後,traits classes有可能在編譯器對類型執行if...else測試

48. 認識template元程式設計

  • Template metaprogramming (TMP,模闆元程式設計)可将工作由運作期移往編譯期,因而得以實作早期錯誤偵測和更高的執行效率
  • TMP可被用來生成“基于政策選擇組合”的客戶定制代碼,也可用來避免生成對某些特殊類型并不适合的代碼

49.了解new-handler的行為

  • set_new_handler允許客戶指定一個函數,在記憶體配置設定無法獲得滿足時被調用
  • Nothrow new是一個頗為局限的工具,因為它隻适用于記憶體配置設定;後繼的構造函數調用還是可能抛出異常

50. 了解new和delete的合理替換時機

  • 有許多理由需要寫個自定的new和delete,包括改善效能、對heap運用錯誤進行調試、收集heap使用資訊

51. 編寫new和delete時需固守正常

  • operator new應該内含一個無窮循環,并在其中嘗試配置設定記憶體,如果它無法滿足記憶體需求,就應該調用new-handler。它也應該有能力處理任何0 bytes申請。Class專屬版本則還應該處理“比正确大小更大的(錯誤)申請”
  • operator delete應該在收到null指針時不做任何事。Class專屬版本則還應該處理“比正确大小更大的(錯誤)申請”

52. 寫了placement new也要寫placement delete

  • 如果沒有這樣做,你的程式可能會發生隐微而時斷時續的記憶體洩露
  • 當聲明placement new和placement delete時,不要無意識地遮掩它們的正常版本

53. 不要輕忽編譯器的警告資訊

54.讓自己熟悉包括TR1在内的标準程式庫(Boost)

55. 讓自己熟悉Boost

繼續閱讀