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实例才会被产生和使用。

猜你喜欢

转载自blog.csdn.net/weixin_28712713/article/details/84738782
6.1
今日推荐