***純虛函式***
在設計抽象基類時,需要注意以下幾點:
(1)不要將destructor宣告為pure virtual function;
如果將destructor宣告為pure virtual function,則設計者一定得定義它。因為每乙個derived class destructor會被編譯器加以擴充套件,以靜態呼叫得方式呼叫其「每乙個virtual base class」以及「上一層base class」的destructor。
(2)不要將那些函式定義內容並不與型別有關的函式設計為virtual function,因為其幾乎不會被後繼的derived class改寫。
(3)對於其derived class可能修改某乙個data member的函式,不應被宣告為const。
***「無繼承」情況下的物件構造***
先定義class point:
class
point
...virtual
float
z();
protected
:float
_x,_y;
} ;
你可不能小看z()這個virtual function給class point帶來的巨大變化。virtual function的引入促使每乙個class point擁有乙個vtpr,這樣一來,編譯器在constructor中新增了對vptr進行初始化的**,而copy constructor和copy assignment operator也會對vptr進行設定,而不再是原先簡單的bitwise操作了。
請看以下的**:
point foobar()
...
將被內部轉化為:
point foobar(point
&_result)
...
從以上**的轉化可以看出:一般而言,如果你的設計之中有很多函式都需要以傳值方式(by value)傳回乙個local class object,那麼提供乙個copy constructor就比較合理。
***繼承體系下的物件構造***
假設class point3d虛擬繼承於class point,但由於class point僅存在乙份實體,因而class point3d的constructor需要注意乙個問題。
請看下面的繼承關係圖:
class
point3d :
virtual
public
point
... ;
class
vertex :
virtual
public
point
... ;
class
vertex3d :
public
point3d,
public
vertex
... ;
通常來說,class point3d和class vertex的constructor均需呼叫point的constructor,然而,當point3d和vertex同為vertex3d的subobject時,它們對point constructor的呼叫操作一定不可以發生,而是交由vertex3d來完成。
那麼如何做到這一點呢?其實只需在constructor中新增乙個引數就可以了。例如,class vertex3d在呼叫point3d和vertex的constructor之前,總是會把引數_most_derived設為false,於是就壓制了兩個constructors中對point constructor的呼叫操作。
//在virtual base class情況下的constructor擴充內容
vertex3d
*vertex3d::vertex3d(vertex3d
*this
, bool
_most_derived,
float
x, float
y, float
z)...
為了控制乙個class中有所作用的函式,編譯器只要簡單地控制住vptr的初始化和設定操作即可。
vptr初始化操作應該如何處理?實際情況是:應該在base class constructors(具體來說,是所有的virtual base classes和上一層的base classes)呼叫操作之後,但是在程式設計師**的碼或是「member initialization list中所列的members初始化操作」之前。為什麼是這樣呢?
如果每乙個constructor都一直等待到其base class constructors執行完畢之後才設定其物件的vptr,那麼每次它都能夠呼叫正確的virtual function實體。
constructor的執行演算法通常如下:
(1)在derived class constructor中,所有virtual base classes的constructor會被呼叫;
(2)在derived class constructor中,上一層base class的constructor會被呼叫;
(3)上述完成之後,物件的vptr(s)被初始化,指向相關的virtual table(s);
(4)如果class有member class object,而後者擁有constructor,那麼它們會以其宣告順序的相反順序被呼叫;
(5)使用者所定義的**。
下面是vptr必須被設定的兩種情況:
(1)當乙個完整的物件被構造起來時,如果我們宣告乙個point物件,point constructor必須設定其vptr;
(2)當乙個subobject constructor呼叫了乙個virtual function(無論是直接呼叫或間接呼叫)時。
在明確了哪些情況下vptr必須被設定,我們在宣告乙個pvertex時,各個vptr不再需要在每乙個base class constructor中被設定。解決之道是把constructor**為乙個完整的object實體和乙個subobject實體。在subobject實體中,vptr的設定可以忽略。
如果我們不對point**乙個copy assignment operator,而光是依賴預設的memberwise copy,編譯器通常不會產生出乙個實體,除非class不表現出bitwise語意。關於哪些情況class不表現出bitwise語意,請參見讀書筆記(2)。
由於在virtual base class的拷貝操作將造成subobject的多重拷貝,並且該問題至今難以解決。因此筆者的建議是:盡量不要允許乙個virtual base class的拷貝操作,甚至建議:不要在任何virtual base class中宣告資料。
***解構語意學***
如果class沒有定義destructor,那麼只有在class內含的member object(或是class自己的base class)擁有destructor的情況下,編譯器才會自動合成出乙個來。
其解構順序與建構順序正好相反。
《深度探索C 物件模型》讀書筆記(5)
純虛函式 在設計抽象基類時,需要注意以下幾點 1 不要將destructor宣告為pure virtual function 如果將destructor宣告為pure virtual function,則設計者一定得定義它。因為每乙個derived class destructor會被編譯器加以擴充...
《深度探索C 物件模型》讀書筆記(6)
物件的構造和解構 一般而言,我們會把object盡可能放置在使用它的那個程式區段附近,這樣做可以節省不必要的物件產生操作和銷毀操作。全域性物件 全域性物件的靜態初始化策略包括以下幾個步驟 1 為每乙個需要靜態初始化的物件產生乙個 sti 函式,內含必要的constructor呼叫操作或inline ...
讀書筆記 《深度探索c 物件模型》 (3)
第四章 function語意學 4.2 虛擬成員函式 a 單一繼承下的virtual function 乙個class只會有乙個virtual table 這個class自己定義的virtual function,它override了乙個可能存在的base class virtual functio...