C++虛解構函式詳解

2020-07-16 10:04:21
我們知道,有時會讓一個基礎類別指標指向用 new 運算子動態生成的派生類物件;同時,用 new 運算子動態生成的物件都是通過 delete 指向它的指標來釋放的。如果一個基礎類別指標指向用 new 運算子動態生成的派生類物件,而釋放該物件時是通過釋放該基礎類別指標來完成的,就可能導致程式不正確。

例如下面的程式:
#include <iostream>
using namespace std;
class CShape  //基礎類別
{
public:
    ~CShape() { cout << "CShape::destrutor" << endl; }
};
class CRectangle : public CShape  //派生類
{
public:
    int w, h;  //寬度和高度
    ~CRectangle() { cout << "CRectangle::destrutor" << endl; }
};
int main()
{
    CShape* p = new CRectangle;
    delete p;
    return 0;
}
程式的輸出結果如下:
CShape::destrutor

輸出結果說明,delete p;只引發了 CShape 類的解構函式被呼叫,沒有引發 CRectangle 類的解構函式被呼叫。這是因為該語句是靜態聯編的,編譯器編譯到此時,不可能知道此時 p 到底指向哪個型別的物件,它只根據 p 的型別是 CShape * 來決定應該呼叫 CShape 類的解構函式。

按理說,delete p;會導致一個 CRectangle 類的物件消亡,應該呼叫 CRectangle 類的解構函式才符合邏輯,否則有可能引發程式的問題。

例如,假設程式需要對 CRetangle 類的物件進行計數,如果此處不呼叫 CRetangle 類的解構函式,就會導致計數不正確。

再如,假設 CRectangle 類的物件在存續期間進行了動態記憶體分配,而釋放記憶體的操作都是在解構函式中進行的,如果此處不呼叫 CRetangle 類的解構函式,就會導致被釋放的物件中動態分配的記憶體以後再也沒有機會回收。

綜上所述,人們希望delete p;這樣的語句能夠聰明地根據 p 所指向的物件執行相應的解構函式。實際上,這也是多型。為了在這種情況下實現多型,C++ 規定,需要將基礎類別的解構函式宣告為虛擬函式,即虛解構函式。

改寫上面程式中的 CShape 類,在解構函式前加 virtual 關鍵字,將其宣告為虛擬函式:
class CShape{
public:
    virtual ~CShape() { cout << "CShape::destrutor" << endl; }
};
則程式的輸出變為:
CRectangle::destrutor
CShape::destrutor

說明 CRetangle 類的解構函式被呼叫了。實際上,派生類的解構函式會自動呼叫基礎類別的解構函式。

只要基礎類別的解構函式是虛擬函式,那麼派生類的解構函式不論是否用virtual關鍵字宣告,都自動成為虛解構函式。

一般來說,一個類如果定義了虛擬函式,則最好將解構函式也定義成虛擬函式。

解構函式可以是虛擬函式,但是建構函式不能是虛擬函式。