C++中虛析構函數的做用html
咱們知道,用C++開發的時候,用來作基類的類的析構函數通常都是虛函數。但是,爲何要這樣作呢?下面用一個小例子來講明: ios
有下面的兩個類:程序員
[cpp] view plain copy安全
輸出結果:app
Do something in class ClxDerived!函數
Output from the destructor of class ClxDerived!this
AAAspa
這個很簡單,很是好理解。
可是,若是把類ClxBase析構函數前的virtual去掉,那輸出結果就是下面的樣子了:.net
Do something in class ClxDerived!設計
AAA
也就是說,類ClxDerived的析構函數根本沒有被調用!通常狀況下類的析構函數裏面都是釋放內存資源,而析構函數不被調用的話就會形成內存泄漏。我想全部的C++程序員都知道這樣的危險性。固然,若是在析構函數中作了其餘工做的話,那你的全部努力也都是白費力氣。
因此,文章開頭的那個問題的答案就是--這樣作是爲了當用一個基類的指針刪除一個派生類的對象時,派生類的析構函數會被調用。
固然,並非要把全部類的析構函數都寫成虛函數。由於當類裏面有虛函數的時候,編譯器會給類添加一個虛函數表,裏面來存放虛函數指針,這樣就會增長類的存儲空間。因此,只有當一個類被用來做爲基類的時候,才把析構函數寫成虛函數。
你須要virtual析構函數嗎
使用VC的class wizard自動生成一個類,會獲得兩個空的函數:構造函數和virtual析構函數。爲何析構函數要聲明成virtual呢?
若是一個類要被使用成多態(polymorphic)的,那麼這個virtual是必須的。好比:
[cpp] view plain copy
delete pa 實際上至關於:
pa->~Animal();
釋放pa所指向的內存(或許是free(pa))。
在 這裏,由於~Animal()是virtual的,儘管是經過Animal類型的指針調用的,根據v-table的信息,~Dog()被正確調用到。若是 把virtual屬性去掉,那麼被調用的是~Animal(),Dog類的構造函數被調用而析構函數未被調用,構造函數中分配的資源沒有釋放,從而產生了 內存泄漏。析構函數缺省聲明爲virtual,就能夠避免這一問題。
可另外一個問題是,有時virtual是不須要的。若是一個類不會被繼承,好比一個utility類,該類徹底是靜態方法;或者一些類儘管可能會被繼承,但不會被使用成多態的,即除了析構函數外,沒有其餘的方法是virtual的,這時就能夠把virtual屬性去掉。
去掉析構函數的virtual屬性後,由於該類中沒有其餘的virtual函數,因此編譯時不會生成v-table,這樣就節省了編譯時間,並減小了最終生成的程序的大小。更重要的是,聽從這一規則,給該類的維護者一個信息,即該類不該被看成多態類使用。
一樣,看成一個抽象時,若是你模仿Java的interface,聲明瞭以下的虛基類:
[cpp] view plain copy
那麼應該給它增長一個空的virtual析構函數:
virtual ~AbstractBase(){}
若是你對COM比較熟悉,可能會注意到,COM interface中並無這個virutal構造函數。這是由於,COM經過使用引用計數的機制來維護對象。當你使用完一個COM對象,調用Release()時,COM的內部實現檢查引用技術是否爲零。若是是,則調用
delete this;
由於Release()是virtual的,因此該COM對象對應的正確的派生類被調用,delete this會調用正確的析構函數,達到了使用virtual析構函數的效果。
定義純虛析構函數(pure virtual destructor)zz
純虛成員函數一般沒有定義;它們是在抽象類中聲明,而後在派生類中實現。好比說下面的例子:
[cpp] view plain copy
可是,在某些狀況下,咱們卻須要定義一個純虛成員函數,而不只僅是聲明它。最多見的例子是純虛析構函數。在聲明純虛析構函數時,不要忘了同時還要定義它。
[cpp] view plain copy
爲何說定義純虛析構函數是很是重要的
派生類的析構函數會自動調用其基類的析構函數。這個過程是遞歸的,最終,抽象類的純虛析構函數也會被調用。
若是純虛析構函數只被聲明而沒有定義,那麼就會形成運行時(runtime)崩潰。(在不少狀況下,這個錯誤會出如今編譯期,但誰也不擔保必定會是這樣。)純虛析構函數的啞元實現(dummy implementation,即空實現)可以保證這樣的代碼的安全性。
[cpp] view plain copy
在某些狀況下定義其它純虛成員函數可能也是很是有用的(好比說在調試應用程序以及記錄應用程序的日誌時)。例如,在一個不該該被調用,可是因爲一個缺陷而被調用的基類中,若是有一個純虛成員函數,那麼咱們能夠爲它提供一個定義。
[cpp] view plain copy
這樣,咱們就能夠記錄全部對純虛函數的調用,而且還能夠定位錯誤代碼;不爲純虛函數提供定義將會致使整個程序無條件地終止。
虛構造函數(virtual constructor)
C++不支持直接的虛構造函數。虛 擬機制的設計目的是使程序員在不徹底瞭解細節(好比只知該類實現了某個界面,而不知該類確切是什麼東東)的狀況下也能使用對象。可是,要創建一個對象,可 不能只知道「這大致上是什麼」就完事——你必須徹底瞭解所有細節,清楚地知道你要創建的對象是究竟什麼。因此,構造函數固然不能是虛的了。可是,可經過虛函數 virtual clone()(對於拷貝構造函數)或虛函數 virtual create()(對於默認構造函數),獲得虛構造函數產生的效果。
注意:子類成員函數clone()的返回值類型故意與父類成員函數clone()的不一樣。這種特徵被稱爲「協變的返回類型」(Covariant Return Types),該特徵最初並非C++語言的一部分,VC6.0如下版本編譯器不支持這樣的寫法。
虛析構函數(virtual destructor)
當你可能經過基類指針刪除派生類對象時,建議使用虛析構函數。虛函數綁定到對象的類的代碼,而不是指針/引用的類。若是基類有虛析構函數,delete basePtr(基類指針)時,*basePtr 的對象類型的析構函數被調用,而不是該指針的類型的析構函數。
簡單講,這個類有虛函數就應該有虛析構函數。一旦你在類中加上了一個虛函數,你就已經須要爲每個對象支付空間代價(每一個對象一個指針),因此這時使析構函數成爲虛擬的一般不會額外付出什麼。
對於那些trivial且沒有子類的類,虛析構函數只會增長開銷,不要使用。