C++之虛析構函數的必要性


構造函數不能是虛函數,主要有兩個原因:

1.派生類不能繼承基類的構造函數,因此把基類的構造函數聲明為虛函數沒有意義,無法實現多態;

2.C++中的構造函數用來的在創建對象的時候進行初始化工作,在執行構造函數的時候,對象尚未創建完成,虛函數表這個時候還不存在,也沒有指向虛函數表的指針,所以此時還無法查詢虛函數表。也就不知道調用哪一個構造函數。

析構函數用來在銷毀對象的時候進行清理工作,可以聲明為虛函數,有時必須聲明為虛函數。

#include<iostream>
using namespace std;

class Base{
public:
        Base();
        ~Base();
protected:
        char *str;
};
Base::Base(){
        str=new char[100];
        cout<<"Base constractor"<<endl;
}
Base::~Base(){
        delete[] str;
        cout<<"Base deconstractor"<<endl;
}

class Derived:public Base{
public:
        Derived();
        ~Derived();
private:
        char *name;
};
Derived::Derived(){
        name=new char[100];
        cout<<"Dervied constractor"<<endl;
}
Derived::~Derived(){
        delete[] name;
        cout<<"Derived deconstractor"<<endl;
}

int main()
{
        Base *pb = new Derived();
        delete pb;
        cout<<"-----------------------"<<endl;
        Derived *pd = new Derived();
        delete pd;
}

執行結果:

Base constractor
Dervied constractor
Base deconstractor
-----------------------
Base constractor
Dervied constractor
Derived deconstractor
Base deconstractor

從運行結果可以看出,語句delete pb;只調用了基類的析構函數,沒有調用派生類的析構函數;而語句delete pd;同時調用了派生類和基類的析構函數。

在本例中,不調用派生類的析構函數會導致 name 指向的 100 個 char 類型的內存空間得不到釋放;除非程序運行結束由操作系統回收,否則就再也沒有機會釋放這些內存。這是典型的內存泄露。

為什么delete pb不會調用派生類的析構函數呢?

因為這里的析構函數是非虛函數,通過指針訪問非虛函數時候,編譯器會根據指針是類型來調用析構函數。也就是說,指針是什么類型,就調用哪個類的析構函數。如這里pb是基類指針,就調用基類的析構函數。

為什么delete pd會同時調用基類和派生類的析構函數?

pd 是派生類的指針,編譯器會根據它的類型匹配到派生類的析構函數,在執行派生類的析構函數的過程中,又會調用基類的析構函數。派生類析構函數始終會調用基類的析構函數。

更改上面的代碼,把析構函數更改虛函數:

#include<iostream>
using namespace std;

class Base{
public:
        Base();
        virtual ~Base();
protected:
        char *str;
};
Base::Base(){
        str=new char[100];
        cout<<"Base constractor"<<endl;
}
Base::~Base(){
        delete[] str;
        cout<<"Base deconstractor"<<endl;
}

class Derived:public Base{
public:
        Derived();
        ~Derived();
private:
        char *name;
};
Derived::Derived(){
        name=new char[100];
        cout<<"Dervied constractor"<<endl;
}
Derived::~Derived(){
        delete[] name;
        cout<<"Derived deconstractor"<<endl;
}

int main()
{
        Base *pb = new Derived();
        delete pb;
        cout<<"-----------------------"<<endl;
        Derived *pd = new Derived();
        delete pd;
}

執行結果:

Base constractor
Dervied constractor
Derived deconstractor
Base deconstractor
-----------------------
Base constractor
Dervied constractor
Derived deconstractor
Base deconstractor

將基類的析構函數聲明為虛函數后,派生類也會自動稱為虛函數,這個時候,編譯器會忽略指針類型,而是根據指針的指向來調用函數,也就是說,指針指向那個對象就調用那個對象的函數。

pb、pd 都指向了派生類的對象,所以會調用派生類的析構函數,繼而再調用基類的析構函數。如此一來也就解決了內存泄露的問題。

 實際開發中,一旦我們自己定義了析構函數,就是希望在對象銷毀時用它來進行清理工作,比如釋放內存、關閉文件等,如果這個類又是一個基類,那么我們就必須將該析構函數聲明為虛函數,否則就有內存泄露的風險。也就是說,大部分情況下都應該將基類的析構函數聲明為虛函數。

注意,這里強調的是基類,如果一個類是最終的類,那就沒必要再聲明為虛函數了。


免責聲明!

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



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