C++ 程序員必經之路 —— 構造、析構、虛析構

/*************************************************/
/*多肽與繼承的測試*/
/*************************************************/
#include <iostream>

using namespace std;

class A 
{
public:
	A() { cout<<"Create A"<<endl; }
	virtual ~A() { cout<<"Destroy A"<<endl; }

	virtual void Print() { cout<<"Print A"<<endl; }
};	// 注意這裏要有分號,我就犯了這個錯誤。

class B : public A
{
public:
	B() { cout<<"Create B"<<endl; }
	~B() { cout<<"Destroy B"<<endl; }

	virtual void Print() { cout<<"Print B"<<endl; }
};

int main(int argc, char* argv[])
{
	B *a = new B();	// [1]或者 A *a = new B();
	// a->Print();	// [2]
	delete(a);		// [3]
	a = NULL;		// [4]

	system("pause");
	return 1; 
}


先看上面的代碼:

在寫下面的內容之前,我覺得首先需要提出:

學習時不要怕犯低級錯誤,每次犯錯,都是一次積累。譬如,在上面代碼裏,我曾就在定義了class A和class B之後忘記了加分號,導致編譯不通過,還找了一會兒才發現這個問題。再如,我在[1]這裏創建對象時,曾經寫成:

        B b = new A();

        這裏一共犯了兩個錯誤,這是JAVA的寫法。

        (1).在c++裏面,new返回的是指針,所以B b應該是B *b纔對。

        (2).第二個錯誤,編譯器會告訴我,不能將對象A轉換成對象B,B繼承了A,如果通過new A()當然不能創建對象B,只能通過B來創建A,可以這樣來理解,因爲B包括了A的所有東西,所以通過類A來創建B,必然會導致B的一些屬性殘缺。正確的應該是: A *a = new B();

 

我想要分享的重點在於以下三點:

1.關於子類和父類構造與虛構調用順序一句話原則:

        構造調用是由上至下(先父類後子類),析構的調用是由下至上(先子類後父類)。

2.虛析構的意義。

3.遇到不能理解的東西,最好的辦法實踐。

----------------------------------------------------------------------------------------------------------------------------------------------

運行上面的代碼,得到的結果如下:

這驗證了第一點:構造的調用順序總是先調用基類,後調用派生類。對象由底層開始創建,由上層開始析構。所以析構的調用順序和構造調用順序是嚴格逆反的。

如果我們把代碼裏class A的析構前的 virtual 關鍵字去掉,再運行一下,結果是這樣的:

沒看錯!沒有發生變化。哈哈,別急,接下來,我們把代碼裏[1]這段代碼替換一下:

        B *a = new B();  ----> A *a = new B();

然後再運行一下:

這下不一樣了!是的,派生類B的析構函數沒有被調用!這樣很可能出現:因爲沒有正確析構而導致內存泄露的問題。

我們再看看百度百科裏,對於虛析構函數的介紹:

虛析構函數是爲了解決基類指針指向派生類對象,並用基類的指針刪除派生類對象。
如果某個類不包含虛函數,那一般是表示它將不作爲一個基類來使用。當一個類不準備作爲基類使用時,使析構函數爲虛一般是個壞主意。因爲它會爲類增加一個虛函數表,使得對象的體積翻倍,還有可能降低其可移植性。
所以基本的一條是:無故的聲明虛析構函數和永遠不去聲明一樣是錯誤的。實際上,很多人這樣總結:當且僅當類裏包含至少一個虛函數的時候纔去聲明虛析構函數。
抽象類是準備被用做基類的,基類必須要有一個虛析構函數,純虛函數會產生抽象類,所以方法很簡單:在想要成爲抽象類的類裏聲明一個純虛析構函數。

這裏:A *a = new B(); a是指向基類A的指針,再析構時,只調用了基類的析構函數,但沒有調用派生類B的析構函數,加入了關鍵字 virtual 後,才能保證只想基類的指針能正確被刪除。

 

好了。這次想要分享的內容差不多就這些了。最後一條是我的切身體會,很多看不懂的東西,拿來實踐一番,一下就明白了。希望這些東西能給大家帶來幫助!


另外分享一點別人寫的關於虛析構的說明,從實現上說明了原因:

虛函數和普通成員函數的區別,是虛函數放在虛函數表中,通過對象的this指針找到該類的虛函數表,然後調用。C++即採用此機制實現多態。如果是普通函數,每個函數的地址是死的。所以用A類的對象調用析構函數時只能調到A的析構。如果是虛函數,則會通過指針找到B的析構函數,而B繼承自A,還會調用A的析構函數。

——摘自:http://blog.csdn.net/cffy625/article/details/5225064


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章