深入探索C++對象模型之五 — 析構、構造、拷貝語意學
一般而言,class的data member應該被初始化,並且只在constructor中或是在class的其它member functions中指定初值,其它任何操作都將破壞封裝性質,使class的維護和修改更加困難。
純虛函數
我們可以定義和調用一個pure virtual function:不過它只能被靜態的調用,不能經由虛擬機制調用。
而對於pure virtual destructor,class設計者必須定義它。因爲每一個derived class destructor都會被編譯器加以擴展,以靜態調用的方式調用其“每一個virtual base class”以及“上一層base class”的destructor。如果缺少任何一個base class destructor的定義,都會導致鏈接失敗。
因此最好不要把virtual destructor聲明爲pure。
無繼承情況下的對象構造
在C之中,全局性的定義被視爲一個“臨時性的定義”,因爲它沒有明確的初始化操作。一個“臨時性的定義”可以在程序中發生多次,那些實例會被鏈接器摺疊起來,只留下一個單獨實體,被放在data segment中的BSS段中。而C++不支持“臨時性定義”,C++中所有全局對象都被當作“初始化過的數據”來對待。
如果要對class中的所有成員都設定常量初值,那麼給予一個explicit initialization list會比較高效。
Explicit Initialization list 帶來三項缺點:
1. 只有當class members都是public時,此法纔有效。
2. 只能指定常量,因此它們在編譯時期就可以被評估求值。
3. 由於編譯器沒有自動施行,所以初始化行爲的失敗可能性會比較高一些。
繼承體系下的對象構造
編譯器會擴充每一個constructor,一般而言擴充操作如下:
- 在memeber initialization list中的data members初始化操作會被放進constructor的函數裏面,以member聲明順序爲順序。
- 如果有member沒有出現在member initialization list當中,而這個member有一個default constructor的話那麼該default constructor也必須被調用。
- 在那之前,如果class object有virtual table pointers,那麼必須設定vptr的初值以指向正確的virtual tables
- 在那之前,所有上一層的base class constructor必須被調用,以base class的聲明順序爲順序。
- 在那之前,所有的virtual base class constructors必須被調用,從左到右,從最深到最淺。
虛擬繼承
虛擬繼承中因爲“base class subobject”共享性之故,那麼在繼承體系中間層面的class一定不能調用virtual base class的constructor,取而代之的是,作爲最底層的class來負責完成“被共享之virtual base subobject”的構造。
那麼編譯器是如何實現上述行爲的呢?我們注意到,“virtual base calss constructor被調用”有着明確的定義,即只有當一個完整的class object被定義出來的時候,它纔會被調用;如果object只是某個完整object的subobject的話那麼它就不會被調用
vptr的初始化
在一個class的constructor中,經由構造中的對象來調用一個virtual function,其函數實體應該是在此class中有作用的那個。爲了能夠找到正確的virtual function, 必須在合適的時機來初始化vptr的內容。
通常constructor的執行算法如下:
- 在derived class constructor中,“所有virtual base classes”以及“上一層base class”的constructor會被調用
- 對象vptr會被初始化,指向相關的virtual tables
- 如果有member initialization list的話,將在constructor中擴展開來,這必須在vptr被設定之後,以免又一個virtual member function被調用。
- 最後執行程序員所提供的代碼
對象複製語意學
當以一個class object 指定另一個class object的時候,如果class沒有表現出bitwise copy的話,那麼編譯器就自動合成一個copy asssignment operator。
class在以下情況不會表現出bitwise語意:
- 當class內帶一個member object,而其class有一個copy assignment operator時。
- 當一個class的base class有一個copy assignment operator時。
- 當一個class生命了任何virtual functions(這種情況下我們不能直接拷貝右端的object的vptr,因爲後者可能是一個derived class object)
- 當class繼承自一個virtual base class時。
析構語意學
如果class沒有定義destructor,那麼只有在class內袋的member object或者自己的base class擁有destructor的情況下,編譯器纔會自動合成一個出來。
編譯器通過合成一個destructor,來保證調用base class destructor,如果class的設計者定義了一個destructor,那麼編譯器也會擴展它。擴展方式和constructor被擴展的方式順序相反:
- destructor函數本體先被執行,也就是說vptr會在程序員的代碼執行前被重設。
- 如果class擁有member class objects, 而後者擁有destructors, 那麼它們會以其聲明順序的相反順序被調用。
- 如果object內帶一個vptr,那麼首先重設相關的virtual table。
- 如果有任何直接的nonvirtual base classes擁有destructor,他們會以聲明順序的相反順序被調用。
- 如果有任何virtual base classes擁有destructor,而當前討論的這個class時最尾端(most-derived)的class,那麼它們會以原來構造順序的相反順序被調用。