多態基類的析構函數應該爲虛函數

#include<iostream>
using namespace std;

class CBird
{
public:
    CBird() { cout << "CBird constructor." << endl; };
    ~CBird() { cout << "CBird destructor." << endl; };
    virtual void fly() { cout << "CBird fly." << endl; };
};

class CLark : public CBird
{
public:
    CLark() { cout << "CLark constructor." << endl; };
    ~CLark() { cout << "CLark destructor." << endl; };
    void fly() { cout << "CLark fly." << endl; }
};

int main()
{
    CBird * pBird = new CLark();
    pBird->fly();
    delete pBird;

    return 0;
}

CBird作爲基類描述鳥類的一般行爲和屬性,因爲不同鳥類的飛行特點不同,所以基類CBird將fly()聲明爲virtrual,希望派生類重寫(overriding)該方法。CLark(lark:百靈鳥)繼承自CBird,並重寫了fly()。

main函數中基類CBird類型指針指向派生類CLark類型對象,並以基類指針調用fly方法,根據c++的多態特性,實際調用的是CLark的fly方法。

可以看到“pBird->fly();”的確調用了派生類CLark的fly方法。但對象析構時只調用了基類CBird的析構函數,卻沒有調用派生類CLark的析構函數,這種現象叫做“部分析構”。

產生這個問題的原因是:當一個派生類對象通過一個基類指針刪除,並且這個基類的析構函數是非虛的,c++將不會調用整個析構函數鏈,結果是未定義的。所以這種情況下,只調用了基類CBird的析構函數,對象的派生部分並沒有被銷燬。

解決辦法就是將多態基類的析構函數設置爲virtual。多態基類指的是基類中至少存在一個virtual函數,具有virtual函數的類也就是想當爹(base class)的類,這樣的類簡稱爲多態基類。將CBird的析構函數設置爲virtual,再看程序的輸出結果。

並不是所有c++類都應該將析構函數設置爲virtual。只有具有virtual函數的多態基類(或者其它想當base class的類)才應該將析構函數設置爲virtual,對於普通的類則無必要。因爲虛函數的實現要求對象攜帶額外信息,也就是維護一個指向虛函數表的指針vptr(virtual table pointer),vptr指向虛函數表vtbl(virtual table)。當調用一個對象的虛函數時,就會通過vptr找到vtbl,在vtbl中尋找正確的函數指針調用。由於vptr的加入,導致對象大小增加。所以對於非多態基類,沒必要將析構函數聲明爲virtual以帶來額外負擔。這同時引出另外一條準則,如果一個類的析構函數非虛,那就說明它不想當爹,程序員要頂住誘惑,拒絕繼承它,即使它“出身名門”,比如標準庫中的string等等。

另外,有時還會將析構函數設置爲純虛函數(pure virtual),擁有純虛函數的類變爲抽象基類(abstract class),抽象基類不能被實例化。如果某個class只希望作爲base class(不希望被實例化),但是又沒有一個純虛函數,而base class應該有一個virtual析構函數,那麼此時就可以將析構函數設置爲純虛函數。

必須爲純虛析構函數提供定義,否則會出現Link錯誤。因爲析構函數的運作方式是,最深層派生(most derived)的那個class其析構函數最先被調用。然後是其每一個base class的析構函數被調用。

【學習資料】 《effective c++》 《編寫高質量代碼 c++》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章