Declare destructors virtual in polymorphic base classes
1、為什麼要申明虛函數 C++程式設計中通常會存在一個基類有多個衍生類別型,而基類中的存在的大都是純虛函數,需要衍生類別型實現。而這樣的情況下,通過使用factory模式返回一個基底類型的指標。在C++中明確指出,一個衍生類別型經過由一個基底類型指標被刪除,而該基底類型帶著一個non-virtual解構函式其結果未定義。只會造成一個局部的銷毀,即基底類型資源被釋放,而衍生類別型造成memory
leak,看下面的代碼:
// Virtual_Const.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class Base{public:Base(){}~Base(){ cout<<"~Base()"<<endl;}////不帶virtual時輸出的結果請看圖一//virtual ~Base(){ cout<<"~Base()"<<endl;}////正確的做法帶virtual時輸出的結果請看圖二};class Derived:public Base{public:Derived(){p=(char*)malloc(sizeof(char)*10);}~Derived(){free(p);cout<<"~Derived()"<<endl;}private:char *p;};int _tmain(int argc, _TCHAR* argv[]){Base *ptr=new Derived();if (ptr==NULL){cout<<"未分配成功"<<endl;exit(1);}delete ptr;return 0;}圖一:不帶virtual時輸出結果:圖二:帶virtual時輸出結果:
以上代碼和輸出結果可以看出,Derived類繼承了Base類,並且在heap上申請了記憶體資源,在它的解構函式中被釋放。但由於Base的解構函式是non-virtual,所以根本沒有正確釋放Derived類型中的ptr指標。
2、virtual 函數實現內部機制 polymorphic(帶多態性質的)base classes應該聲明一個virtual解構函式。如果class帶有任何virtual函數,它就應該擁有一個virtual解構函式。Classes的設計目的如果不是作為base
classes使用,或不是為了具備多態性(polymorphically),就不應該聲明virtual解構函式。那麼是不是所有的class都加上virtual解構函式以保萬一,事實不是如此。如果沒有必要,請不要為解構函式加上virtual二字。原因是virtual是有代價的,為了實現virtual函數,類中間必須要增加一個pointer指向虛函數表,這樣增大了類的體積。所以沒有必要的話,還是不要隨意聲明virtual的解構函式。普遍的規則是只有當類當中有virtual的函數時,解構函式才聲明為virtual。也就是說這個基類是有多態性質(polymorphic)的。 虛函數的實現需要它所在的對象包含額外的資訊,這一資訊用來在運行時確定本對象需要調用哪個虛函數。通常,這一資訊採取一個指標的形式,這個指標被稱為“ vptr ”(“虛函數表指標”)。 vptr 指向一個包含函數指標的數組,這一數組稱為“ vtbl ”(“虛函數表”),每個包含虛函數的類都有一個與之相關的 vtbl 。當一個虛函數被一個對象調用時,就用到了該對象的
vptr 所指向的 vtbl ,在 vtbl 中尋找一個合適的函數指標,然後調用相應的實函數。 請看如下代碼,就知道聲明virtual函數是要浪費系統資源的。
// sev_vir.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class class1{};class class2{public:virtual ~ class2();};int _tmain(int argc, _TCHAR* argv[]){ cout<<sizeof(class1)<<endl; cout<<sizeof(class2)<<endl; return 0;}
3、請注意
不過還有一個問題,那就是繼承那些不帶virtual函數的標準類庫的類,比如string類。如果你繼承了string類,那麼當你使用string的指標釋放你的繼承類的話,依然存在上面描述的問題,你的繼承類沒有釋放它該釋放的空間。因為string沒有virtual的解構函式。所以請不要去繼承這些並沒有virtual解構函式的類。
還有就是STL中的容器通常繼承都毫無意義,因為它們也都沒有提供virtual解構函式。C++並沒有提供像JAVA或者C#的final和sealed這樣的約束關鍵字來禁止某個類型被繼承。 並不是每個類型都會被用來做繼承用途,也不是每個類型設計出來被用來做為多態用途,所以正確的設計相當重要。
4、純虛函數實現
// Pure_vir.cpp : 定義控制台應用程式的進入點。//#include "stdafx.h"#include <iostream>using namespace std;class Base //abstract class{public: virtual ~Base(){};//virtual, not pure virtual void func() const = 0;//pure virtual };void Base::func() const //pure virtual also can have function body{ cout <<"Base::func"<<endl;}class Derived : public Base{public: Derived(){} virtual void func() const { Base::func(); cout <<"Derived::func"<<endl; } virtual void foo(){}};int _tmain(int argc, _TCHAR* argv[]){ Base* pb=new Derived(); pb->func(); pb->Base::func(); return 0;} 從產生的結果我們可以看到有純虛函數的類是抽象類別,不能產生對象,只能派生。
如果他派生的類的純虛函數沒有被改寫,那麼,它的衍生類別還是個抽象類別。定義純虛函數就是為了讓基類不可執行個體化化, 因為執行個體化這樣的抽象資料結構本身並沒有意義。或者給出實現也沒有意義實際上我個人認為純虛函數的引入 ,為了安全.因為避免任何需要明確但是因為不小心而導致的未知的結果,提醒子類去做應做的實現.。
請記住:
1、polymorphic(帶多態性質的) base classes應該聲明一個virtual 解構函式。如果class帶有任何virtual函數,它就應該擁有一個virtual解構函式。
2、Classes的設計目的如果不是作為base classes使用,或不是為了具備多態性質(polymorphically),就不該聲明virtual解構函式。