多態基類聲明virtual析構函數

  • 問題發現?

在維護一份原有代碼時,看到類似下面的代碼:(模型簡化版)

#include<iostream>
using namespace std;

//定義一個純虛基類
class Base 
{
public:
    Base() 
    {  
        cout<< "Construct Base!"<< endl;
    };
    ~Base()     //NOTE: non-virtual 
    {
        cout<< "Destruct Base!"<< endl;
    };      

    virtual void DoSomething() = 0;
};


//實現派生類
class Derived : public Base
{
public:
    Derived() 
    { 
        cout<< "Construct Derived!" <<endl;
    };
    ~Derived() 
    { 
        cout<< "Destruct Derived!" <<endl; 
    };   

    void DoSomething() 
    { 
        cout<< "Do whatever in Derived class!" <<endl; 
    };
};

這個實現有問題嗎? 讓我們簡單運行一下看看:

int main()
{
    //case1: 使用基類指針指向派生類對象
    Base *pBase =  new Derived;
    pBase->DoSomething();
    delete pBase;
    pBase = NULL;

    //case2: 使用派生類指針指向派生類對象
    Derived* pDerived = new Derived;
    pDerived = dynamic_cast<Base*>(pDerived);
    pDerived ->DoSomething();
    delete pDerived ;  
    pDerived = NULL;

    return 0;
}

下面是運行結果:

這裏寫圖片描述

結果出現了詭異的“局部銷燬”現象,case1中的對象在delete 時沒有調用派生類Derived的析構函數,這將很可能造成內存資源泄露。

問題出現在Derived 對象被基類Base 指針刪除,而目前的基類Base有個non-virtual 析構函數,這將引起一個災難性結果,因爲C++明確指出:當Derived class對象經由一個Base class 指針被刪除,而該Base class 帶着non-virtual 析構函數時,其結果是未定義的,實際執行時通常發生對象的Derived成分沒有被銷燬。

  • 怎麼解決呢?

給Base class 一個virtual 的析構函數,此後刪除Derived class 對象時就按照預期銷燬整個對象,包括所有Derived class。可以更改後自行測試一下,此處不再粘貼結果。

像Base class 這樣除了析構函數之外通常還有其他virtual函數,此處virtual目的是允許Derived class 的實現得以具體化,在不同的Derived class中有不同的實現代碼。任何class 只要帶有virtual函數基本都應該明確也應有一個virtual 析構函數。

  • 問題延伸

某些情況下,欲使一個類成爲抽象類,但剛好又沒有任何純虛函數。怎麼辦? 實際上,抽象類的產生也可由純虛析構函數實現。

抽象類是準備被用做基類的,如上面所講,基類必須要有一個virtual析構函數,在沒有純虛函數會產生抽象類的情況下,可以藉助於聲明一個純虛析構函數實現:

class AbstractBase 
{
public:
  virtual ~AbstractBase () = 0;   // 聲明一個純虛析構函數
};

該AbstractBase 類有一個純虛函數,是抽象類,而且有一個virtual析構函數,因此不會產生析構函數導致對象釋放不全的問題。

除此聲明之外,因爲要實例化其派生類對象,還必須提供純虛析構函數的定義, 即:

AbstractBase::~AbstractBase () {} // 純虛析構函數的定義

而且該定義是必需的,因爲虛析構函數工作的方式是:最底層的派生類的析構函數最先被調用,然後各個基類的析構函數被調用。也就是說,即使是抽象類,編譯器也要產生對~AbstractBase() 的調用,因此必須要保證爲它提供實現函數體,否則,編譯連接時就會報錯,直到實現該函數體爲止,示例代碼如下:

class AbstractBase
{ 
public: 
    virtual ~AbstractBase() = 0; 
}; 

class ConcreteDerived :public AbstractBase 
{ 
public: 
    virtual ~ConcreteDerived(){};
}; 

//AbstractBase的純虛函數實現體,此時AbstractBase仍爲抽象類 
AbstractBase::~AbstractBase(){};  //必須提供,否則編譯報錯
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章