構造函數不能是虛函數,主要有兩個原因:
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 都指向了派生類的對象,所以會調用派生類的析構函數,繼而再調用基類的析構函數。如此一來也就解決了內存泄露的問題。
實際開發中,一旦我們自己定義了析構函數,就是希望在對象銷毀時用它來進行清理工作,比如釋放內存、關閉文件等,如果這個類又是一個基類,那么我們就必須將該析構函數聲明為虛函數,否則就有內存泄露的風險。也就是說,大部分情況下都應該將基類的析構函數聲明為虛函數。
注意,這里強調的是基類,如果一個類是最終的類,那就沒必要再聲明為虛函數了。