構造函數、析構函數、虛析構函數、純虛析構函數要點總結

1、派生類的構造函數、析構函數默認會調用基類的構造函數、析構函數。

順序:基類構造、派生類構造;派生類析構,基類析構。

舉例:

/******************************************************************************************************
* File:Constructor&DestructorTest
* Introduction:測試構造函數、析構函數、虛構造函數與純虛構造函數的一些特性。
* Author:CoderCong
* Date:20141013
* LastModifiedDate:20160113
*******************************************************************************************************/
#include "stdafx.h"
#include <iostream>
using namespace std;
class Base
{
public:
	Base()
	{
		cout << "Base:Constructing" << endl;
	}
	~Base()
	{
		cout << "Base: Destructing!" << endl;
	}
};
class Derived:public Base
{
public:
	Derived()
	{
		cout << "Derived:Constructing" << endl;
	}
	~Derived()
	{
		cout << "Derived: Destructing!" << endl;
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Derived *pDerived=new Derived;
	delete pDerived;
	return 0;
}

運行結果:
Base:Constructing 
Derived:Constructing 
Derived: Destructing! 
Base: Destructing!

2、如果你的類用作派生類的基類,最好定義其析構函數爲虛函數。

好處:定義其析構函數爲虛函數,可以利用動態綁定,把一個子類對象綁定到一個基類指針上時,可以自動調用基類的析構函數。從而默認地調用基類的析構函數。

舉例: 

同樣是上邊的函數,我們把_tmain函數改爲這樣:

	Base *pBase = new Derived;
	delete pBase;
	

同志們注意運行結果: 

Base:Constructing 
Derived:Constructing 
Base: Destructing!

天哪!居然沒有調用派生類的析構函數! 
注意,你delete的指針是Base類的指針。而Base的析構函數不是虛函數。當然不用調用派生類的析構函數! 
天真的你也許會說,這也沒什麼大不了的吧?

沒什麼大不了的?再也沒有比這更大的事情了。這可能會造成一個嚴峻的問題:內存泄露!我們真正想刪除的是一個派生類對象,而實際上只是刪除了派生類對象中基類的那一部分。如果派生部分有一些需要釋放的資源,如網絡、內存等,那麼這些資源將無法得到釋放。

作爲一個嚴謹的C++程序員,我們當然不允許這樣的事情發生。我們把Base類的析構函數改爲虛函數試試看:

virtual ~Base()
	{
		cout << "Base: Destructing!" << endl;
	}
運行結果:
Base:Constructing 
Derived:Constructing 
Derived: Destructing! 
Base: Destructing!

我們驚喜得發現,一切都正常了。

再來理一遍:

我們new一個Derived類對象,調用了Derived類的構造函數,而此構造函數又默認地調用了基類Base的析構函數;

然後,我們把這個Derived對象綁定到Base類指針pBase,這時候,這個pBase的靜態類型是Base*,動態類型卻是Derived*。我們刪除pBase,系統跟據動態綁定,最終調用的是~Derived,而不是~Base。調用~Derived之後,系統又默認地調用了基類析構函數~Base。

就是這樣。

3、爲什麼構造函數不能是虛函數?

當我們想把構造函數也設爲虛函數的時候,會發現,系統是不允許的?爲什麼呢?
查過資料之後,發現這麼一段解釋:
    A virtual call is a mechanism to get work done given partial information. In particular, "virtual" allows us to call a function knowing only an interfaces and not the exact type of the object. To create an object you need complete information. Inparticular, you need to know the exact type of what you want tocreate. Consequently, a "call to a constructor" cannot be virtual.

翻譯一下:虛函數只需要”部分“信息就可以自動調用,特別地,這種機制允許我們在只知道接口,不知道具體對象的類型的情況下調用函數。而實例化一個對象需要完整的信息,特別地,必須知道實例化對象的確切類型。總之,構造函數的調用不能是虛的。
這下子我們就知道爲什麼了。具體虛函數的實現機制,我們隨後的博文中會介紹。今天我們只講構造函數和析構函數的事。

4、基類有純虛構造函數時爲什麼會連接錯誤?

上代碼:
virtual ~Base() =0;

這時,我們在實例化一個派生類對象時,發生連接錯誤,如下圖所示:
                               
這是爲什麼呢?我們想一下在調用Derived類的析構函數的時候發生了什麼?首先調用~Derived,因爲我們並沒有動這個函數,所以這一步是沒有問題的。之後,又調用了~Base,可是系統找不到Base的函數體?這就是問題所在了吧?不確定,我們試試看。
把一個空的~Base的函數體加上看:
Base::~Base(){}
程序正常運行了。說明我們的猜測是正確的。
就是這樣。

打破罐子問到底,多問爲什麼?爲什麼要用純虛析構函數,有什麼用?
我在想,純虛函數是爲了解決什麼問題?純虛函數是爲了實現抽象類,也就是說,某個基類,我們不想用戶將這個類實例化,最簡便的方法,就是將這個基類中的某個函數定義爲純虛函數。
所以,當這個基類中只有構造和析構怎麼辦?只能拿析構函數來開涮了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章