天天看點

為什麼要用虛析構函數

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 ,也可以是所有子類的析構函數變成虛函數。

注:

為了友善對比,現給出包含多個虛函數的虛函數表,如下:

為什麼要用虛析構函數