《Effective C++》閱讀總結(四): 設計、宣告與實現

zq發表於2022-05-29

第四章: 設計與宣告

18. 讓介面更容易被正確使用,不易被誤用

  • 將你的class的public介面設計的符合class所扮演的角色,必要時不僅對傳參型別限制,還對傳參的值域進一步限制。

19. 設計class猶如設計type

  • 內建型別如int、float等,本質也是一個class,使用者自定義的class的行為和狀態應當與內建型別類似的。設計class時,首先要考慮構造和析構、然後是賦值操作如何實現、考慮class是否要繼承某一已有class、儘量使你的class一般化等等需要考慮的問題。

20. 寧以pass-by-reference-to-const替代pass-by-value

  • 這個準則很常見,使用const引用傳參以避免拷貝和修改入參。看具體場景,如果是需要修改入參,那麼就不要加const、如果入參是簡單內建型別,拷貝不怎麼消耗資源,那麼直接傳值也是可以的。同樣,使用指標也是一樣的道理。此外,傳class的引用或指標也可以避免物件切個問題,例如函式接受一個父類物件,當傳入子類物件時,父類物件建構函式會被呼叫,此時子類的屬性就會丟失,即切割問題,這是不希望的。

21. 必須返回物件時,不要妄想返回reference

  • 很顯然的一點就是,你不能返回一個locakl物件的指標或者引用,因為,函式呼叫結束後,所有儲存在棧上的local物件都將被銷燬。此外,可以返回在函式內構造的堆物件的指標,但儘量不要返回其引用,引用如果在返回後沒有保護好而被覆蓋,則造成記憶體洩漏。

22. 將成員變數宣告為private

  • 這是一個封裝合理的class應當遵守的規則,成員變數宣告為private的話,可以通過public介面間接控制成員變數,並加以特殊限制。這樣做也有缺點,就是編寫程式碼無法直接修改成員變數,程式碼量增加。如果只需要儲存讀取資料,不做其他操作,那麼宣告一個結構體是一個不錯的選擇。
    我們一把不使用protected成員,沒什麼用。

23. 寧以non-membernon-friend替換member函式

  • 如果一個操作是一般性的,並不是class特有的,那麼將其抽離class單獨宣告定義。

24. 若所有引數都需要型別轉換,請為此採用non-member函式

  • 這裡的所有引數包括this指標,所以連this指標都需要轉換型別,那麼這個函式其實就不應該是這個class的成員函式,應當抽離class。

25. 考慮寫一個不丟擲異常的swap函式

  • 使用std::swap吧,我覺得目前夠用了

第五章 實現

我們寫c++程式碼,一個是如何設計架構,即定義class及其成員函式和成員資料,以及不同class之間的通訊關係;另一個是如何具體實現每個函式,對每個函式或成員函式的功能進行實現,這部分每個功能相對獨立,比較底層,但其中也有些需要注意的點,主要有以下幾個:

26. 儘可能延後變數定義式的出現時間

  • 這樣可以提高程式的效率,但我並不認為會提高程式的可讀性。對於那些不是非常在意執行效率的函式,可讀可維護性要排在效率前面,如初始化,而那些會迴圈呼叫很多次的程式碼,如模型推理計算,效率至上。此外,編譯器也會盡可能優化程式碼,以提高執行時效率。

27. 儘量少做轉型動作

C++中的cast方法有四種:
const_cast:用於移除物件身上的const屬性,只此一個功能。常用。低風險。
static_cast:用於強制隱式轉換。例如將int轉為double,將基類指標轉為子類指標時不進行安全檢查。不可用於移除const屬性。常用。低風險。
dynamic_cast:用於執行類繼承體系中安全向下轉型。也就是用來決定某個物件是否歸屬類繼承體系中的某個型別。比如可以將多型基類(包含虛擬函式的基類)的指標強制轉換為派生類的指標,很耗時,不常用。高風險。
reinterpret_cast:用於執行低階轉型,例如將int*轉為int,執行的是逐個位元複製的操作。 不用。高風險。

  • 所以,首先儘量避免轉型,或者對於不可避免的轉型,將轉型操作隱藏到函式裡面,並且儘量使用C++style的轉型方法,不要使用早期C風格的那種方法。在類繼承體系中進行上行轉換時,dynamic_caststatic_cast效果一樣且安全,但下行轉換時,dynamic_cast會進行型別安全檢查且耗時,而static_cast不進行檢查。所以如果有必要且明確基類指標是哪個子類時,通常使用static_cast即可。
    移除const屬性是危險的,如無十分必要請不要這樣做,肯定有其他方法規避這種危險操作。

28. 避免返回handle指向物件的內部成分

  • 即不要返回指向物件資料成員的指標或引用,以保證物件的封裝性,防止外部改變物件內部資料。如果非要這樣做,請將返回值加上const屬性,以禁止修改。

29. 為異常安全所做的努力是值得的

  • 這條準則不好總結,大概來說就是對那些可能導致異常發生的函式進行異常捕獲並進行適當的處理,以避免記憶體洩漏。

30. 透徹瞭解inline的裡裡外外

  • 直接定義在class體內的成員函式預設是內聯的,也可以在class體外顯式宣告行內函數,但最終是否執行內聯替換由編譯器決定,它只會將那些小型、頻繁呼叫的函式編譯為行內函數,這樣能儘可能降低程式碼膨脹並提升程式執行速度。

31. 將檔案間的編譯依存關係降至最低

  • 即讓程式碼依賴宣告式而非定義式,這樣定義式發生改變不會導致依賴於該定義式的其他程式碼也需要重新編譯。基於此構想的兩種方案是:Handle classinterface class。這是在我們平時構建工程的時候經常遇到的兩種設計方法。
  • 程式庫標頭檔案應該以“完全且僅有宣告式”的形式存在。其他程式碼依賴此檔案即可。

小結:

以上即總結。明天上班。

相關文章