4.4 C++虛析構函數
參考:http://www.weixueyuan.net/view/6373.html
總結:
構造函數是不能聲明為虛函數的,析構函數可以被聲明為虛函數。
將基類的析構函數聲明為虛函數之後,派生類的析構函數也自動成為虛析構函數。
未將基類的析構函數定義為虛函數,如下面的例子的情況可能會出現內存泄漏。原因是不構成多態,函數屬於編譯期綁定,無論基類指針p指向的是派生類對象或者是基類對象,執行的都將會是基類的函數。
通常來說,如果基類中存在一個指向動態分配內存的成員變量,並且基類的析構函數中定義了釋放該動態分配內存的代碼,則應該將基類的析構函數聲明為虛函數。
只有非靜態成員函數才可以成為虛函數,而靜態成員函數不能聲明為虛函數。
在類中,構造函數用於初始化對象及相關操作,構造函數是不能聲明為虛函數的,因為在執行構造函數前對象尚未完成創建,虛函數表尚不存在,此時就無法去查詢虛函數表,因此也就無法得知該調用哪一個構造函數了。
析構函數則用於銷毀對象時完成相應的資源釋放工作,析構函數可以被聲明為虛函數。我們先通過一個例子來說明析構函數聲明為虛函數的必要性。
例1:
#include<iostream> using namespace std; class base { public: base(); ~base(); private: int * a; }; class derived: publicbase { public: derived(); ~derived(); private: int * b; }; base::base() { cout<<"base constructor!"<<endl; a = new int[10]; } base::~base() { cout<<"base destructor!"<<endl; delete[] a; } derived::derived() { cout<<"derived constructor!"<<endl; b = new int[1000]; } derived::~derived() { cout<<"derived destructor!"<<endl; delete[] b; } int main() { base* p; p = new derived; delete p; return 0; }
在本類中定義了兩個類,一個基類,一個派生類,派生類和基類中都分別定義了自己的構造函數和析構函數。基類和派生類中各有一個int型指針成員變量,在基類的構造函數中,給指針變量a分配了10個int型空間,在基類的析構函數則用於將是將a所指向的空間釋放掉,在派生類的構造函數中,指針成員變量被分配了1000個整型空間,派生類的析構函數則是為了釋放掉b指針所指向的存儲空間。在主函數中,我們創建一個基類類型的指針,指針指向一個派生類對象,之後釋放p指針所指向的對象的存儲空間。最後程序運行結果如下:
base constructor!
derived constructor!
base destructor!
觀察程序運行結果,程序打印出了“base constructor!”這串字符,則說明基類的構造函數被調用了,之後又打印出了“derived constructor!”這串字符,同樣地派生類的構造函數也被調用了。當我們用new操作符創建一個派生類對象時會先調用基類構造函數,然後再調用派生類構造函數,程序輸出結果與我們料想的是一致的。至此基類的成員變量a通過構造函數被分配了10個整型存儲空間,派生類的成員變量b通過構造函數被分配了1000個整型存儲空間。之後程序打印出了“base destructor!”字符串,這說明基類的析構函數被調用了,a指針所指向的10個整型內存空間被釋放了。但是之後卻並未調用派生類的析構函數,不調用派生類的析構函數則會導致b指針所指向的1000個整型存儲空間不會被釋放,如此一來造成了內存泄露了。內存泄露問題肯定是我們程序設計人員需要極力避免的。本例中出現的問題就是因為派生類的析構函數未被調用,為了解決這個問題,我們將基類的析構函數聲明為虛函數,修改後基類的定義如下:
class base { public: base(); virtual ~base(); private: int * a; };
修改基類的定義後,程序運行結果如下:
base constructor!
derived constructor!
derived destructor!
base destructor!
將基類的析構函數聲明為虛函數之後,派生類的析構函數也自動成為虛析構函數,在主函數中基類指針p指向的是派生類對象,當delete釋放p指針所指向的存儲空間時,會執行派生類的析構函數,派生類的析構函數執行完之後會緊接著執行基類的析構函數,以釋放從基類繼承過來的成員變量所消耗的資源。如此一來就不會存在內存泄漏問題了。
從此例中我們很明顯可以看出析構函數聲明為虛函數的必要性,但是如果不管三七二十一的將所有的基類的析構函數都聲明為虛函數,這也是不合適的。通常來說,如果基類中存在一個指向動態分配內存的成員變量,並且基類的析構函數中定義了釋放該動態分配內存的代碼,則應該將基類的析構函數聲明為虛函數。
只有非靜態成員函數才可以成為虛函數,而靜態成員函數不能聲明為虛函數。
例1:
class test { public : virtual test(){a = new int[5];} //error static void g(); //ok virtual void f(); //ok virtual static void h(); //compile error virtual ~test(){delete[] a;} //ok private: int * a; };
在本例中定義了一個test類,這個類中有一個指針成員變量a,test類中有五個成員函數,在本例中將析構函數和普通成員函數f聲明為虛函數是沒有問題的,將構造函數和靜態成員函數聲明為虛函數則會出現編譯錯誤,這兩種做法都是有違C++語法規定的。
4.4 C++虛析構函數