c++ 虛函數 構造函數 析構函數

一、虛函數和構造函數

        當創建一個含有虛函數的對像是,必須初始化它的VPTR以指向相應的VTABLE,這必須在對虛函數進行任何調用之前完成,而設置VPTR這項工作是由構造函數來完成。編譯器在構造函數的開頭部分祕密地插入能初始化VPTR的代碼,如果我們沒有爲一個類顯式創建構造函數,則編譯器會爲我們生成構造函數。如果該類含有虛函數,則生成的構造函數將會包含相應的VPTR初始化代碼。

        所有基類構造函數總是在繼承類構造函數中被調用,這個是有意義的,因爲構造函數有一項專門的工作:確保對象被正確地建立。派生類只訪問它自己的成員,而不訪問基類的成員,只有基類構造函數能正確地初始化它自己的成員。因此,確保所有的構造函數被調用是很關鍵的,否則整個對象不會使當地被構造,這就是爲什麼編譯器強制爲派生類的每個部分調用構造函數的原因,如果不在構造函數初始化列表中顯示地調用基類構造函數,它就調用默認構造函數,如果沒有默認構造函數,編譯器將報告出錯。

二、構造函數不能爲虛函數

        當繼承時,必須知道基類的全部成員並能訪問基類的任何Public和Protected成員。這意味着,當在派生類中時,必須能肯定基類的所有成員都是有效的。在構造函數中,爲了保證對象的所有成員已經建立,唯一的方法就是讓基類構造函數首先被調用。這樣,當在派生類構造函數中,在基類中能訪問的所有成員都已經被初始化,這也是下面做法的原因:只要可能,我們應當在這個構造函數初始化表達式所有的成員對象(即對象通過組合被置於類中)。

       爲什麼構造函數不能是虛函數?因爲對於在構造函數中調用一個虛函數,被調用的知識這個函數的本地版本,也就是說,虛機制在構造函數中不工作。

       這種行爲有以下兩種理由:

       第一: 概念上來說,在任何構造函數中,我們是先讓基類構造函數首先被調用,然後調用成員對象構造函數。然而,虛函數可以調用在派生類中函數,如果我們的構造函數也這麼做,那麼我們所調用的函數可能操作還沒有被初始化的成員,這將導致災難的產生。

       第二:從機制上來說,當一個構造函數被調用時,它做的首要的事情就是初始化它的VPTR。然而,他只能知道它屬於“當前類”——即構造函數所在的類。所以,當編譯器爲這個構造函數產生代碼時,它是爲這個類的構造函數產生代碼——既不是爲基類,也不是委它的派生類,所以它使用的VPTR必須是對於這個類的VTABLE。因此,只要它不是最後的構造函數調用,那麼在這個對象的生命期,VPTR將保持被初始化爲指向這個VTABLE,依此類推,直到最後的構造函數結束。VPTR的狀態是由被最後的構造函數確定的,這也就是爲什麼構造函數調用是按照從基類到最後派生的類的順序的另一個理由。

       但是,當這一系列構造函數調用正發生時,每個構造函數都已經設置VPTR指向它自己的VTABLE,如果函數使用虛機制,它將只產生通過它自己的VTABLE的調用,而不是最後派生的VTABLE(所有構造函數被調用後纔會有最後派生的VTABLE)。

       綜合以上兩點得出: 構造函數是不能爲虛函數的。

三、析構函數能夠且常常必須是虛的

       與構造函數調用的順序完全相反,析構函數從最晚派生的類開始,並向上到基類。這是安全且合理的:當期的析構函數一直知道基類成員任然是有效的。如果需要在析構函數調用某一基類的成員函數,進行這樣的操作是安全的。因此,析構函數能夠對其自身進行清除,然後調用下一個析構函數,該析構函數又將執行它的清除工作,依次類推。

      應當記住,構造函數和析構函數是類層次進行調用的唯一地方(因此,編譯器自動地生成適當的類層次)。在所有的其它函數中,只有這個函數會被調用(非基類版本),而無論是虛的還是非虛的。同一個函數的基類版本在普通函數中北調用(無論虛函數還是非虛函數)的唯一方法就是顯式地調用這個函數。

      如果將析構函數設置爲非虛函數,那麼我們通過指向基類的指針操縱派生類對象的析構函數時,編譯器只能知道調用這個析構函數的基類版本,派生類自己的析構函數部分不會被調用,這樣會引入“內存泄露”。

因此,析構函數可以且常常必須是虛函數。

四、純虛析構函數

        經過純虛析構函數在標準C++中是合法的,但是使用時有個額外的限制:必須爲純虛析構函數提供一個函數體。這可能聽起來有點違反常規,因爲通常的純虛函數沒有函數體的,但是因爲在一個類層次中總是會調用所有的析構函數,如果不對一個純虛析構函數進行定義,在析構期間將不知道該調用什麼函數體。因此,編譯器和鏈接程序強迫純虛析構函數一定要有一個函數體,這是十分必要的。

        然而,當從某個含有純虛析構函數的類中繼承出一個類,就跟其它的純虛函數有所不同,我們不要求在派生類中提供純虛函數的定義。此外,通常如果在派生類中基類的純虛函數沒有重新定義,則派生類將會成爲抽象類,但是,對於純析構函數而言,並不是這樣。

// Pure virtual destructors ,seem to behave strangely

class AbstractBase {

public:

  virtual~AbstractBase() = 0;

};

 

AbstractBase::~AbstractBase() {}

 

class Derived : public AbstractBase {};

// No overriding of destructor necessary?

 

int main() { Derived d; }///:~

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