簡述 將各個功能拆分後分別封裝(各功能解耦),需要時可自由組合(包括執行順序) 話不多說,看個優化案例吧。 優化案例 最初版 以下是模擬客戶端想服務端發送請求的業務流程。 客戶端調用代碼如下。 // 客戶端 public class Client { public static void main( ...
這篇博客來總結一下《深度探索C++對象模型》第5章構造、析構、拷貝語義學的內容。
是對主要內容的總結,原文請看原書。
1. 構造函數
按照發生的順序,一個類的構造函數會做的事情:
- 所有虛基類的構造函數會被調用,從左到右,從深到淺:
- 如果虛基類被列在member initialization list(成員初始化列表)中,那麼如果有任何明確指定的參數,都應該傳遞過去;如果沒有列在list中,而該類有default constructor,也應該調用;
- 此外,類中每一個virtual base class subobject(虛基類子對象,此處我寫為“子對象”意為其是該類的一部分)的偏移量(offset)必須在執行期可被存取(也就是虛表指針必須被設置好,其中有虛基類偏移量);
- 如果class object是最底層的(most-derived,也就是最後的派生類),其constructor可能被調用,某些用以支持這個行為的機制必須被放進來。
- 所有上一層的base class constructor必須被調用,以base class的聲明順序為順序,跟其在member iniaialization list中的順序無關;
- 如果base class被列於member initialization list中,那麼任何明確指定的參數都應該被傳遞過去;
- 如果base class沒有被列於member initialization list中,而他有default constructor,那麼就調用(書中還提到或者有default memberwise copy constructor就調用,沒有參數為什麼調用拷貝構造函數?我認為可能是一個錯誤);
- 如果base class是第二或後繼的base class,那麼this指針需要被調整。
- 如果class object有virtual table pointer(s)(虛表指針),它們必須被設定初值,指向適當的virtual table;
- 記錄在member initialization list(成員初始化列表)中的data member(數據成員,即非靜態成員變數)初始化操作會被放進構造函數本身,並以成員的聲明順序為順序(進行初始化);
- 如果某個成員變數沒有出現在member initialization list中,但在聲明時被賦予了初值,則設置此初值(或調用構造函數)【這是我自己加的,C++11之前不允許在聲明中設置初值】;
- 如果有一個成員變數沒有出現在member initialization list中,但他有default constructor,那麼會調用它;
- 構造函數中用戶提供的語句被執行。
這個順序給了我們以一個啟發:那就是類的構造是從深到淺的,從內到外構造的過程就是該類從基類到派生類的提升過程。所以在構造函數中儘量不要使用虛函數,因為此時類的構造可能還未結束(如果此類被一個派生類繼承,此時虛函數指針還未被正確設置),此時調用虛函數最多會調用自己定義的的虛函數和自己子類的虛函數,而外層可能的派生類定義的虛函數則不會被調用。
2. 對象複製語義學
一個class對於預設的copy assignment operator,在以下情況不會表現出bitwise copy(按位拷貝,就是直接拷貝)語義:
- 當class內帶一個member object,其class有一個copy assignment operator時;
- 當一個class的base class有一個copy assignment operator時;
- 當一個類聲明瞭任何virtual function時(我們一定不能拷貝右端class object的vptr,因為它可能是一個derived class object(派生類對象));
- 當類繼承自一個virtual base class (不論此base class有沒有copy operator,因為虛繼承也意味著一定有vptr)。
3. 解構語義學
按照發生的順序,一個類的析構函數會做的事情:
- 如果一個object內帶有vptr,那麼首先重設相關的vptr;
- destructor的函數本身現在被執行,也就是說vptr會在用戶代碼執行前被重設(reset);
- 如果class擁有member class objects,而後者有構造函數,那麼它們會以其聲明的順序的相反順序被調用;
- 如果有任何直接的(上一層)nonvirtual base class擁有destructor,那麼它們會以其聲明順序的相反順序被調用;
- 如果有任何virtual base class 擁有destructor,而當前討論的這個class是最尾端(most-derived)的class,那麼它們會以原來的構造順序的相反順序被調用。
在書中作者說析構函數順序是構造函數順序的相反順序,因此譯者認為作者寫錯了,順序是:2, 3, 1, 4, 5。即vptr的設置放在子類析構之後。但是其實上面12345的順序是對的。
從其流程來看,和構造函數一樣,我們也不應該在析構函數里調用虛函數,因為此時vptr被設置為本類的vptr,而不是可能的派生類的vptr。
其他比如純虛函數就不說了,我覺得用處好像不是很大。