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


#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++》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM