1 問題引出:為什麼要使用虛析構函數?
class A //父親
{
public:
~A()
{
cout << "調用了父親的析構函數"<<endl;
}
};
class B : public A //兒子
{
public:
~B()
{
cout << "調用了兒子的析構函數" << endl;
}
};
int main()
{
A *p;
p = new B;
delete p;
system("pause");
return ;
}
運作結果:
我們知道在delete p; 中 delete 操作符,會調用對象的析構函數,但是這裡傳入的是父類對象指針,是以delete 此時并不知道應該調用哪個析構函數,保險起見則隻調用父類的析構函數,也就是說如果沒有使用虛析構函數,那麼一般情況下隻會去析構父類,而不會去析構子類。是以當delete p; 的時候,就會發生記憶體洩漏,也進而産生了異常。
注:
上面是傳入父類指針并對其直接析構,如果不是對父類指針(或者引用)直接析構那一般不會出錯,如 void main() {B b;A *p;p=&c;} 這時雖然使用了父類指針,但不是對父類指針析構,而是很明确直接析構b,是以用不用虛函數,也可以正确析構,
如果某個類不包含虛函數,那一般是表示它将不作為一個基類來使用。當一個類不準備作為基類使用時,使析構函數為虛一般是個壞主意。因為它會為類增加一個虛函數表,使得對象的體積翻倍,還有可能降低其可移植性。
是以基本的一條是:無故的聲明虛析構函數和永遠不去聲明一樣是錯誤的。實際上,很多人這樣總結:當且僅當類裡包含至少一個虛函數的時候才去聲明虛析構函數。
2 那使用虛析構函數會有什麼效果?
我們把析構函數前加上virtual關鍵字,來看一下效果。
class A //父親
{
public:
virtual ~A()
{
cout << "調用了父親的析構函數"<<endl;
}
};
class B : public A //兒子
{
public:
~B()
{
cout << "調用了兒子的析構函數" << endl;
}
};
int main()
{
A *p;
p = new B;
delete p;
system("pause");
return ;
}
運作結果如下:
在使用虛析構函數後,delete 明顯變聰明了,知道應該先析構兒子,再去析構父親了。但是在B類(兒子類)的析構函數刻意沒有加 virtual 關鍵字,可見如同其他虛函數一樣,隻要父類加 virtual 就可以了。
引申:
虛析構函數,也是通過vftable來實作的,實際上當父類的析構函數聲明為虛函數時,子類的析構函數也變成了虛函數(雖然兩者函數名不同),即告訴編譯器,我和我的派生類都需要動态(運作時)完成析構函數執行。
在編譯器中我們可以看到:
析構函數的名字做了特殊處理,換成了‘vector deleting destructor’,用特定的标記,來标明析構函數。是以即使函數名不同,隻要父類中使用 virtual ,也可以是所有子類的析構函數變成虛函數。
注:
為了友善對比,現給出包含多個虛函數的虛函數表,如下: