1. 程式人生 > >6.1 物件的構造和析構(Object Costruction and Destruction)

6.1 物件的構造和析構(Object Costruction and Destruction)

一般而言,constructor和destructor的安插如你所預期那樣:

//C++ pseudo
{
	Point point;
	//point.Point::Point(); 一般會被安插在這裡
	...
	//point.Point::~Point(); 一般會被安插在這裡
}

如果一個區間(以{}括起來的區間)或函式中有一個以上的離開點,情況會稍微複雜點。Destructor必須被放在每一個離開點(當時object還存活)之前:

{
	Point point;
	//constructor在這裡
	switch(int(point.x()))
	{
		case -1:
			//muble
			//destructor在這裡
			return;
		case 0:
			//muble
			//destructor在這裡
			return;
		case 1:
			//muble
			//destructor在這裡
			return;
		default:
			//muble
			//destructor在這裡
			return;
			
	}
	//destructor在這裡
}

在上述例子中,point的destructor必須在switch指令的4個出口的return操作之前被生成出來。另外也很可能在這個區間的結束符(右大括號)之前被生成出來——即使程式分析的結果發現絕不會進行到那裡。

同樣的道理,goto指令也可能需要多個destructor呼叫操作,例如下面程式片段:

{
	if(cache)
		return 1;
	Point xx;
	//xx的constructor在這裡
	
	while(cvs.iter(xx))
	if(xx == value)
		goto found;
	
	//xx的destructor在這裡
	return 0;
	
found:
	//cache item
	//xx的destructor在這裡
	return 1;
}

Destructor呼叫操作必須被放在最後兩個return指令之前,但是卻不必放在最初的return之前,因為那是object尚未被定義出來。

一般而言我們會盡量把object放在使用它的那個程式區間附近,這麼做可以節約非必要的物件產生操作和摧毀操作。以本例而言,如果我們檢查cache之前就定義了Point object就不夠理想。

全域性物件(Global Objects)

如下程式片段:

Matrix identity;

main
{
	//identity必須在此處被初始化
	Matrix m1 = identity;
	...
	return 0;
}

C++保證,一定會在main函式中第一次用到identity之前,把identity構造出來;在main()函式結束之前把identity析構掉。像identity這樣所謂的gobal object如果有constructor和destructor的話,我們說它需要靜態的初始化操作和記憶體釋放操作。

C++程式中,所有的global objects都被放置在程式的data segment中,如果顯示指定它一個值,此object以此值初始化;否則object將初始化為0。

區域性靜態物件(Local Static Objects)

對於如下片段:

const Matrix& identity()
{
	static Matrix mat_identity;
	//...
	return mat_identity;
}

Local static class object保證如下的語意:

  • mat_identity的constructor只能被施行一次,雖然上述函式可能被多次呼叫。
  • mat_identity的destructor只能被施行一次,雖然上述函式可能被多次呼叫。

編譯器的策略之一就是,無條件的在程式起始(startup)時構造出物件來(現在C++ Standard已經強制要求這一點)。

物件陣列(Array of Objects)

假如我們定義瞭如下的陣列:

Point knots[10];

如果Point沒有定義constructor和destructor,這樣工作就像建立內建型別資料一樣;如果定義了default destructor,所以這些destructor必須輪流實施於每一個元素上。一般而言經由一個或多個runtime library函式達成。

對於cfront編譯器而言,使用一個命名為vec_new()函式來產生出以class objects構造而成的陣列。比較新的編譯器,比如Borland、Microsoft和Sun則提供兩個函式,一個來處理“沒有virtual base class”的class,另一個用來處理“內含virtual base class”的class。後一個函式被稱為vec_vnew()。函式型別通常如下:

void* vec_new(
	void* array,		//陣列起始地址
	size_t elem_size,	//每個class object的大小
	int elem_count,		//陣列中元素個數
	void (*constructor)(void*),
	void (*destructor)(void*,char)
)

其中的constructor和destructor引數是這個class的default constructor和default destructor的函式指標。引數array持有具名陣列(knots)的地址或者就是0,如果是0,這是new運算子生成的。

引數elem_size表示陣列中的元素個數,在vec_new()中,constructor被施行於elem_count個元素之上。對於支援exception handling的編譯器,destructor的提供是必要的。下嗎是編譯器可能對我們10個Point元素所做的vec_new()呼叫操作:

Point knots[10];
vec_new(&knots,sizeof(Point),10,&Point::Point,0);

如果Point也定義了destructor,當knots的生命結束時,該destructor也必須施行於10個Point身上,這個經由類似vec_delete()(或是一個vec_vdelete()——如果classes擁有virtual base class的話)的runtime Library函式完成:

void* vec_delete(
	void* array,		//陣列起始地址
	size_t elem_size,	//每個class object的大小
	int elem_count,		//陣列中元素個數
	void (*destructor)(void*,char)
)

有些編譯器會另外增加一些引數,用以傳遞其他數值,以便能夠有條件地導引vec_delete()的邏輯。在vec_delete()中,destructor被施行於elem_count個元素身上。

如果程式設計師顯示提供一個或多個明顯的經由class objects組成的陣列:

Point knots[10] = {
	Point(),
	Point(1.0,1.0,0.5),
	-1.0
};

對於那些明顯獲得初值的元素,vec_new()不再有必要。對於那些尚未初始化的元素,vec_new()的施行方法就像面對“由class elements組成的陣列,而該陣列沒有explicit initialization list”一樣。因此上一個定義很可能被轉換為:

Point knots[10];

//C++ pseudo code
//顯示初始化前三個元素
Point::Point(&knots[0]);
Point::Point(&knots[1],1.0,1.0,0.5);
Point::Point(&knots[2],-1.0,0.0,0.0);

//以vec_new初始化後7個元素
vec_new(&knots+3,sizeof(Point),7,&Point::Point,0);

Default Constructor和陣列

如果你想在程式中取一個constructor的地址是不行的。這是編譯器在支援vec_new()情況下該做的事情。然而,經由一個指標來啟動constructor,將無法存取default argument values。

例如,在cfront2.0之前,宣告一個由class objects所組成的陣列,意味著這個class必須沒有宣告constructors或一個default constructor(沒有引數那種)。一個constructor不可以取一個或一個以上的預設引數。如下:

class complex{
	complex(double = 0.0,double = 0.0);
};

當時的語言規則下無法宣告一個由complex class objects組成的陣列,在2.0版本,修改了語言規則就可以支援了:

complex c_array[10];


//轉化上述語句,編譯器最終呼叫
vec_new(&c_array,sizeof(complex),10,&complex,0);

預設的參如何對vec_new()起作用的方法:cfront採用的方法是產生一個內部的stub constructor,沒有引數。在其內部呼叫由程式設計師提供的constructor,並將default 引數顯示地指定:

complex::complex()
{
	complex(0.0,0.0);
}

編譯器又一次違反了明顯語言規則:class 支援兩個沒有單引數的constructors。只有class objects陣列真正被產生出來的時,stub例項才會被產生和使用。