C++構造函數析構函數虛函數

在C++中,創建派生類對象時,基類和派生類的構造函數和析構函數的調用順序正好相反:

    對於構造函數,先執行基類的,再執行派生類的子對象成員的,最後執行派生類的構造函數體。

    對於析構函數,先執行派生類的析構函數體,再執行派生類的子對象成員的,最後執行基類的。

注意:

  1. 對基類成員和派生類子對象成員的初始化必須在成員初始化列表中進行,新增成員的初始化既可以在成員初始化列表中進行,也可以在構造函數體中進行;
  2. 派生類的析構函數和沒有繼承關係的類中析構函數的定義完全相同,只要在函數體中負責把派生類新增的非對象成員的清理工作做好就夠了,系統會自己調用基類及成員對象的析構函數,來對基類及對象成員進行清理;
  3. 當派生類有多個基類時,處於同一層次的各個基類的構造函數的調用順序取決於定義派生類時聲明的順序(自左向右),而與在派生類構造函數的成員初始化列表中給出的順序無關。
  4. 當派生類中有多個子對象時,各個子對象構造函數的調用順序也取決於在派生類中定義的順序(自前至後),而與在派生類構造函數的成員初始化列表中給出的順序無關。

派生類構造函數和析構函數構建的原則:

  1. 基類的構造函數和析構函數不能被派生類繼承;
  2. 如果基類沒有定義構造函數,派生類也可以不定義構造函數,全部採用缺省的構造函數,此時派生類新增成員的初始化工作可用其它公有函數來完成;
  3. 如果基類定義了帶有形參表的構造函數,派生類就必須定義新的構造函數,提供一個將參數傳遞給基類構造函數的途徑,以便保證基類在初始化時獲得必需的數據;
  4. 如果派生類的基類也是派生類,則每個派生類只負責其直接基類的構造,不負責自己的間接基類的構造;
  5. 派生類是否要定義析構函數與所屬的基類無關,如果派生類對象在撤銷時需要做清理善後工作,就需要定義新的析構函數。

派生類的數據成員是所有基類的數據成員和派生類新增的數據成員共同組成。如果派生類中還有對象成員,派生類的數據成員中還間接包含有這些對象的數據成員。

先看下面的例子:

#include "stdafx.h"
#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		foo();
	}
	~Base()
	{
		printf("!base\n");
		foo();
	} 
	void foo()
	{
		printf("base\n");
	}
};

class der:public Base
{
public:
	der()
	{
		foo();
	}
	~der()
	{
		printf("!der\n");
		foo();
	}
	void foo()
	{
		printf("der\n");
	}
};


int _tmain(int argc, _TCHAR* argv[])
{
	der * p = new der;
	delete p;
	printf("-----------------------\n");
	Base * q = new der;
	delete q;

	return 0;
}

程序運行的結果如下:

兩段代碼都是使用new運算符動態創建了一個派生類對象並賦值給指針,並通過指針刪除派生類對象,不同的是一個是派生類自己的指針,一個是基類的指針。

通過派生類指針刪除對象的運行結果跟前面提到的析構函數的調用順一致,比較奇怪的是爲什麼通過基類base指針刪除派生類der對象時,沒有調用派生類的析構函數?

這是因爲在在公有繼承中,基類對派生類及其對象的操作,只能影響到那些從基類繼承下來的成員。如果想要用基類對非繼承成員進行操作,則要把基類的這個操作(函數)定義爲虛函數。

那麼析構函數也就如此,如果它想析構子類中的重新定義或新的成員對象,那麼它應該聲明爲虛的。

但有一點要注意:虛函數(包括虛析構函數)增加內存開銷。(見http://www.dzsc.com/data/html/2010-5-20/83289.html

也就是說:如果基類的析構函數不是虛析構函數的話,就會只調用基類base中的析構函數釋放資源, 而不會調用派生類der的析構函數, 這時派生類der的資源沒有被釋放,從而導致大量內存泄露。

因此解決這個問題的方法就是把基類的析構函數聲明爲虛的,即在虛構函數前添加關鍵字virtual,定義爲虛析構函數時當用delete(delete只能釋放用new動態分配的內存)釋放派生類的資源時就會根據基類的析構函數自動調用派生類中的析構函數釋放派生類的資源。並且這種虛屬性是自動被繼承的,即只要基類中的析構函數是虛析構函數 ,則該基類的派生類中的析構函數自動爲虛析構函數 ,雖然派生類中的析構函數前沒有virtual關見字,析構函數名字也不一樣,但派生類中的析 構函數被自動繼承爲虛析構函數 。

#include "stdafx.h"
#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		foo();
	}
	virtual ~Base()
	{
		printf("!base\n");
		foo();
	} 
	void foo()
	{
		printf("base\n");
	}
};

class der:public Base
{
public:
	der()
	{
		foo();
	}
	~der()
	{
		printf("!der\n");
		foo();
	}
	void foo()
	{
		printf("der\n");
	}
};

class derder : public der
{
public:
	derder()
	{
		foo();
	}
	~derder()
	{
		printf("!derder\n");
		foo();
	}
	void foo()
	{
		printf("derder\n");
	}
};


int _tmain(int argc, _TCHAR* argv[])
{
	der * q = new der;
	delete q;
	printf("-----------------------\n");
	Base * p = new der;
	delete p;
	printf("-----------------------\n");
	der * r = new derder;
	delete r;
	return 0;
}

運行結果:

接下來我們來考慮如果把foo函數設置爲虛函數又會出現什麼情況呢?

#include "stdafx.h"
#include <iostream>
using namespace std;

class Base
{
public:
	Base()
	{
		foo();
	}
	~Base()
	{
		printf("!base\n");
		foo();
	} 
	virtual void foo()
	{
		printf("base foo\n");
	}
};

class der:public Base
{
public:
	der()
	{
		foo();
	}
	~der()
	{
		printf("!der\n");
		foo();
	}
	virtual void foo()
	{
		printf("der foo\n");
	}
};



int _tmain(int argc, _TCHAR* argv[])
{
	der * q = new der;
	delete q;
	printf("-----------------------\n");
	Base * p = new der;
	delete p;
	return 0;
}

 運行結果:

遵循的原則還是基類的析構函數是否是虛的。

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