實現中的小細節

26:postpone variable definition as long as possible.
定義變量意味着將調用其構造函數、析構函數。
定義變量最好的時機是真的需要使用變量前!甚至能在能確定初值之前!這樣可避免調用默認構造函數、在調用賦值操作,替換爲直接使用拷貝構造函數,提高效率!
特殊的場景:
僅在循環中使用的變量,是定義在循環體之外還是 循環之內呢?
在這裏插入圖片描述成本分析:
一次構造+一次析構+n次賦值
n次拷貝+n次析構
但通常來說,方法A聲明的變量作用域更大,容易對程序可理解性和可維護性造成衝突。一般情況下,方法B更可取。

27:minimize casting
C++新式轉型語法:
const_cast(expression):轉除對象的常量性
dynamic_cast(expression):執行安全向下轉型,決定對象是否歸屬繼承體系中某個類型,這種轉型消耗計算資源,能不用就不用
static_cast(expression):隱式轉換,創建副本。

理解成員函數與調用成員函數的對象:
成員函數只有一份,關鍵是不同對象去調用成員函數,成員函數有個this指針,指向調用函數的對象
關於使用轉型的錯誤用法:
在這裏插入圖片描述正確寫法:
在這裏插入圖片描述再來分析一下dynamic_cast(expression):什麼場景下會用到這個轉型?我們知道一個指向基類的指針實際指向的派生類對象,並想在這個指針上執行派生類所有的行爲,爲保證編譯通過,需要先將其轉爲派生類型。

如何避免使用dynamic_cast(expression)?
在基類定義有可能的派生類想做的事(定義缺省實現的虛函數),如此一來使用基類指針即可實現動態綁定。
一定要避免連串的dynamic_cast(expression),這會使得代碼冗長,且系統難以維護,每當有新的派生類加入繼承層次中,都需要重新改寫這部分,這對用戶代碼來說極爲不友善。
在這裏插入圖片描述在這裏插入圖片描述28.avoid returning “handles” to object internals
reference 、指針、迭代器都屬於handle,用來取得某個對象。
返回一個指向數據成員的handle,使得調用者有機會來修改對象內部的數據成員,降低封裝性。
在這裏插入圖片描述在這裏插入圖片描述錯誤寫法,企圖修改靜態對象的數據成員在這裏插入圖片描述返回handle的另一個問題是,用戶容易產生懸空指針。無法控制客戶端代碼,但類設計包含返回返回handle這類的接口,客戶可能對臨時對象調用此類函數獲得handle,並賦值給某變量,但臨時變量一旦別釋放,很容易產生懸空指針。
在這裏插入圖片描述29:strive for exception-safe code
exception safe code:發生異常後,帶有異常安全性的函數可以保證資源不泄露、數據不被破壞。
數據不被破壞有三種程度的保證:
基本保證:保證數據的一致性,但客戶無法預期會出現哪種情況,由類設計着決定。
強烈保證:類的設計者和用戶達成一致共識:函數成功調用,數據狀態正常改變,一旦函數內部發生異常,數據狀態恢復到調用之前的狀態。
不拋擲異常:內置的數據類型上的操作可以保證不拋出異常

一般化的策略來獲取強烈保證:copy and swap
將需要修改數據成員封裝爲一個類,創建對象這個待修改成員的副本,在副本上做出修改,再swap。
在這裏插入圖片描述能實現異常的強烈安全保證固然好,但是實現起來過於複雜,如果一個函數內部調用多個函數,且不說有些函數只提供異常的基本保證,那麼整個函數都不會提供強烈異常安全保證。甚至所有的子函數都提供異常強烈安全保證,不發生異常將改變數據狀態,但凡是其中一個函數發生了異常,需要成功執行的函數恢復到之前的狀態也是很難做到的。
如果編寫函數時不在意異常安全性,那麼整個程序都不具備異常安全性。因此,我們儘可能保證所編寫的函數具有異常安全性。

在這裏插入圖片描述item30:understand the ins and outs of inlining
inline 只是對編譯器的一種申請:分爲隱式申請(函數在類內部進行定義)和顯示申請(將inline明確寫出)
template 函數和inline都定義在頭文件中,大多數建置環境,在編譯期間進行inlining,模板函數也是類似,在編譯期間需要具現化。

既然inline只是申請,那麼編譯器何時會拒絕inline函數的申請?
1、函數過於複雜:帶有遞歸、循環
2、virtual 函數是inline ,具體執行哪個版本virtual函數只有在運行期間才能決定,而inline在編譯期間就要決定。
3、使用函數指針調用inline函數,inline函數沒有函數地址,因此編譯器還是會生成一個outlined的函數本體。
在這裏插入圖片描述
構造函數、析構函數看似什麼都沒有做,其實不然!對於派生類,編譯器自動調用基類的構造函數,自動初始化派生類的成員,並將這些操作放入到構造函數或是析構函數中,因此構造函數、析構函數的代碼量也許並不小。

inline 函數好處:
1、省去函數調用成本
2、編譯器最優化機制針對不含函數調用代碼
inline 函數可能帶來的問題:
1、使用函數本體替換調用,因此目標碼會增大
2、一旦修改inline函數,將導致所有使用inline函數的客戶代碼都需要重新編譯,而不是用inline函數,意味着客戶只需要進行連接即可
3、inline函數不好調試

在調試程序初期,除非必要不要將函數inlined!而爲了獲取效率:應該將常用的小體積函數聲明爲inline!

在這裏插入圖片描述31:minimize compilation dependencies between files
問題在於類的定義式包含接口,也包含部分實現(數據成員的定義,編譯器必須要知道數據成員類型的定義式),接口和實現沒有完全分開。爲了得知數據成員類型的定義式,通常會#include相應的頭文件。因此一旦改變某個類的實現,由於#include將有可能使得大量的頭文件需要重新編譯。

爲了解決文件間的依存性,這裏提出兩種方式:基於handle和基於抽象基類。
基於handle:將一個類分爲類實現和類接口兩部分。類的數據成員是一個指向實現的指針,類的接口的實現全權轉交給其實現類完成。**接口類和實現類接口完全相同。**重點在於僅在實現類修改,客戶#include僅是接口類,完全不會受影響。
幹嘛要多次一舉呢?
客戶只能使用接口類頭文件,我們修改都只在是實現類內部進行,因此只要接口不變,包含接口頭文件的文件就不需要重編譯。

在這裏插入圖片描述在這裏插入圖片描述基於抽象基類:頭文件僅包含抽象基類的聲明,僅包含接口完全不含實現,客戶#include該頭文件,此時除非修改抽象基類的接口將導致客戶文件的重編譯 ,不會帶來編譯的依賴性。
類的設計者派生該抽象基類,實現虛函數和定義數據成員。所有客戶使用的是包含該抽象基類的頭文件,實現被隔離到派生類中。
在這裏插入圖片描述
在這裏插入圖片描述
創建派生類(頭文件的實現)後,定義基類的create(),派生類相當於頭文件的實現.cpp
在這裏插入圖片描述爲什麼要返回基類handle?爲了實現多態性,客戶使用可能如下,客戶全憑基類指針調用虛函數:
在這裏插入圖片描述這一節的目的是幫助我們在設計類的時候將實現和接口分離,而我們設計的類給客戶使用的
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章