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例項才會被產生和使用。