C++中爲什麼有時基類的析構函數必須寫爲虛函數

C++中爲什麼有時基類的析構函數必須寫爲虛函數

    說到虛函數之前,先簡單解釋一下多態,我的這篇博文:http://blog.csdn.net/zcyzsy/article/details/52463355  裏有很詳細的解釋多態,這裏簡單說下:

不同對象調用相同名稱的成員函數時,可能引起不同的行爲(即執行不同的代碼)。這種現象稱爲多態性。將函數調用連接相應函數體的代碼的過程稱爲函數聯編(簡稱聯編)。C++中根據聯編時刻不同,分爲靜態聯編和動態聯編。

 

靜態聯遍:不同的類可以有相同名稱的成員函數,編譯器在編譯時對他們進行函數聯編,這種在編譯時刻進行的聯編稱爲靜態聯編。靜態聯編所支持的多態性就是編譯時多態性(也稱編譯期多態性、靜態多態性)。函數重載和運算符重載就屬於編譯時多態性。

 

動態聯編:在動態聯編中,在程序運行時才能確定調用哪個函數。這種在運行時的函數聯編稱爲動態聯編。動態聯編所支持的多態性就是運行時多態性(也稱運行期多態性、動態多態性)C++中,只有虛函數纔可能是動態聯編的。可以通過定義類的虛函數和創建派生類,然後在派生類中重新實現虛函數,實現具有運行時的多態性。

   綜上,知道虛函數的作用了,那麼爲什麼析構函數有時要聲明爲虛函數?

編譯器總是根據類型來調用類成員函數。但是一個派生類的指針可以安全地轉化爲一個基類的指針。這樣刪除一個基類的指針的時候,C++不管這個指針指向一個基類對象還是一個派生類的對象,調用的都是基類的析構函數而不是派生類的。如果你依賴於派生類的析構函數的代碼來釋放資源,而沒有重載析構函數,那麼會有資源泄漏。所以通常是將析構函數聲明爲虛函數。一個基類的函數一旦聲明爲虛函數,那麼不管你是否加上virtual修飾符,它在所有派生類中都成爲虛函數。但是由於理解明確起見最好還是加上virtual修飾符。      

     

簡言之:在實現多態時,當用基類指針操作派生類對象時,在析構時防止只析構基類而不析構派生類的狀況發生,因此將基類的析構函數聲明爲虛函數。

 

測試代碼如下:

1

#include<iostream>
using namespace std;
class BaseClass
{
public:
    BaseClass() {};
    ~BaseClass() { cout << "this isdestructor of BaseClass!" << endl; };
    void fun() { cout << "this is fun of BaseClass" << endl; };
};
 
class DerivedClass:public  BaseClass
{
public:
    DerivedClass() {};
    ~DerivedClass() { cout << "this isdestructor of DerivedClass!" << endl; };
    void fun() { cout << "this is fun of DerivedClass" << endl; };
};
 
int main()
{
    DerivedClass *p = new DerivedClass;
    p->fun();
    delete p;
    system("pause");
    return 0;
}


 

運行結果:

this is fun ofDerivedClass

this isdestructor of DerivedClass!

this isdestructor of BaseClass!

 

這段代碼中基類的析構函數不是虛函數,main函數中用派生類的指針去調用派生類的函數,釋放指針P的過程是:先釋派生類的資源,再釋放基類資源

 而換成BaseClass* p=new BaseClass;

運行結果:

this is fun of BaseClass

this isdestructor of BaseClass!

 

2.

#include<iostream>
using namespace std;
class BaseClass
{
public:
    BaseClass() {};
    ~BaseClass() { cout << "this isdestructor of BaseClass!" << endl; };
    void fun() { cout << "this is fun of BaseClass" << endl; };
};
 
class DerivedClass:public  BaseClass
{
public:
    DerivedClass() {};
    ~DerivedClass() { cout << "this isdestructor of DerivedClass!" << endl; };
    void fun() { cout << "this is fun of DerivedClass" << endl; };
};
 
int main()
{
    BaseClass *p = new DerivedClass;
    p->fun();
    delete p;
    system("pause");
    return 0;
} 


輸出結果:

this is fun ofBaseClass

this isdestructor of BaseClass!    

這段代碼中基類的析構函數同樣不是虛函數,不同的是在main函數中用基類的指針指向派生類對象,釋放指針P的過程是:只是釋放了基類的資源,而沒有調用派生類的析構函數.調用fun()函數執行的也是基類定義的函數(沒有實現多態).這樣的delete只能夠刪除基類對象,而不能刪除子類對象,會造成內存泄漏。

    在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員.如果想要用基類對非繼承成員進行操作,則要把基類的這個函數定義爲虛函數。析構函數自然也應該如此:如果它想析構子類中的重新定義或新的成員及對象,當然也應該聲明爲虛的。

 

3.

#include<iostream>
using namespace std;
class BaseClass
{
public:
    BaseClass() {};
    virtual ~BaseClass() { cout << "this isdestructor of BaseClass!" << endl; };
    virtual void fun() { cout << "this is fun of BaseClass" << endl; };
};
 
class DerivedClass:public  BaseClass
{
public:
    DerivedClass() {};
    ~DerivedClass() { cout << "this isdestructor of DerivedClass!" << endl; };
    void fun() { cout << "this is fun of DerivedClass" << endl; };
};
 
int main()
{
    BaseClass *p = new DerivedClass;
    p->fun();
    delete p;
    system("pause");
    return 0;
}

  

運行結果:

this is fun of DerivedClass

this is destructorof DerivedClass!

this isdestructor of BaseClass!   

 這段代碼中基類的析構函數被定義爲虛函數,main函數中用基類的指針去操作派生類的成員,釋放指針P的過程是:先釋放了派生類的資源,再調用基類的析構函數.調用fun()函數執行的也是繼承類定義的函數.  

 

4.

#include<iostream>
using namespace std;
class BaseClass
{
public:
    BaseClass() {};
    virtual ~BaseClass() { cout << "this isdestructor of BaseClass!" << endl; };
    virtual void fun() { cout << "this is fun of BaseClass" << endl; };
};
 
class DerivedClass:public  BaseClass
{
public:
    DerivedClass() {};
    ~DerivedClass() { cout << "this isdestructor of DerivedClass!" << endl; };
    void fun() { cout << "this is fun of DerivedClass" << endl; };
};
 
int main()
{
    DerivedClass *p = new DerivedClass;
    p->fun();
    delete p;
    system("pause");
    return 0;
}


運行結果:

this is fun ofDerivedClass

this isdestructor of DerivedClass!

this isdestructor of BaseClass!

如上代碼,fun() 不是虛函數,析構函數也不是虛函數,結果依然如此。

 

如上,一直用的是有時,而不是一定聲明爲虛函數,爲什麼呢?可不可以把析構函數默認爲虛函數?

答:

如果不需要基類對派生類及對象進行操作,則不能定義虛函數,因爲這樣會增加內存開銷.當類裏面有定義虛函數的時候,編譯器會給類添加一個虛函數表,裏面來存放虛函數指針,這樣就會增加類的存儲空間.所以,只有當一個類被用來作爲基類的時候,才把析構函數寫成虛函數.

   C++不把虛析構函數直接作爲默認值的原因是虛函數表的開銷以及和C語言的類型的兼容性。有虛函數的對象總是在開始的位置包含一個隱含的虛函數表指針成員。

 

 


發佈了68 篇原創文章 · 獲贊 216 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章