对象的构造和析构

本文围绕C++展开,介绍了constructor和destructor的安插情况,如在有多个离开点的区段或函数中,destructor需放在合适位置。还阐述了全局对象的静态初始化和内存释放方法,局部静态对象构造和析构的执行条件,以及对象数组构造和析构时runtime library函数的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.

一般而言,constructor和destructor的安插都如你预期的那样:

{
	Point point;
	//point.Point::Point()一般而言放在这里
	...
	//point.Point::~Point()一般而言放在这里
}

如果一个区段(以{}括起来的区域)或函数中由一个以上的离开点,情况就不同了。Destructor必须被放在每一个离开点(当时object还存活)之前,比如:

{
	Point point;
	//constructor在这里行动
	switch (int(point.x())) {
	case -1:
		//destructor在这里
		return;
	case 0:
		//destructor在这里
		return;
	case 1:
		//destructor在这里
		return;
	case default:
		//destructor在这里
		return;
	}
	//destructor在这里
}

在这个例子中,point的destructor必须在switch指令4个出口的return操作前被生成出来。另外也很有可能在这个区段的结束符号(右大括号)之前被生成出来。
再看下面这个例子:

{
	if (cache)
		return 1;
	Point xx;
	//xx的constructor在这里行动
	if (xx == value)
		//xx的destructor在这里行动
		return 1;
	else
		//xx的destructor在这里行动
		return 0;
}

Destructor调用操作必须放在最后两个return指令之前,但是却不必放在第一个return之前,因为那时object尚未被定义出来。
一般而言会把object尽可能放置在使用它的哪个程序区段附近,这么做可以节省非必要对象产生操作和摧毁操作。以本例为例,如果在检查cache之前就定义了Point object,那就不够理想。很多次需要在使用C++的时候,仍然习惯把所有objects放在函数或某个区段的起始处

2.全局对象

class GLOBAL
{
public:
     GLOBAL() { cout << "我被构造了"<<endl; }
     ~GLOBAL() { cout << "我被析构了" << endl; }
};


GLOBAL a;

int main()
{
     cout << "Begin()" << endl;



     cout << "End()" << endl;

     return 0;
}

在这里插入图片描述
这样的所谓的global obgect如果有constructor和destructor的话,我们就说它需要静态的初始化操作和内存释放操作。
下面是静态初始化方法:

1.为每一个需要静态初始化的档案产生一个_sti()函数,内带必要的constructor调用操作或 inline expansions.例如前面所说的identity对象会在matrix.c中产生出下面的_sti()函数(sti可能是 static initialization的缩写):
其中matrix_c是文件名编码,_identity表示文件中所定义的第一个 static object.在__sti之后附加上这两个名称,可以为可执行文件提供一个独一无二的识别符号.
2.类似情况,在每一个需要静态的内存释放操作(static deallocation)的文件组宏,产生出一个__std()函数(std可能是 static deallocation的缩写),内带必要的destructor调用操作,或是其 inline expansions.在例子中会有一个__std()函数被产生出来,针对identity对象调用Matrix destructor.
3.提供一组runtime library "munch"函数:一个_main()函数(用以调用可执行文件中的所有的__sti()函数),以及一个exit()函数(以类似方式调用所有的__std()函数).
在这里插入图片描述

2.局部静态对象

void TestLocalStatic()
{
     cout << "TestLocalStatic() Begin()" << endl;

     static GLOBAL a;

     cout << "TestLocalStatic() End()" << endl;
}


int main()
{
     cout << "Begin()" << endl;

     TestLocalStatic();
     TestLocalStatic();
     TestLocalStatic();
     TestLocalStatic();


     cout << "End()" << endl;

     return 0;
}

在这里插入图片描述

constructor必须只能施行一次,虽然上述函数可能会被调用多次.
destructor必须只能施行一次,虽然上述函数可能会被调用多次.
因此,只在identity()被调用时才把mat_identity构造出来,是比较好的做法.

首先导入一个临时性对象以保护mat_identity的初始化操作.第一次处理identity()时,这个临时对象被评估为 false,于是constructor会被调用,然后临时对象被改为 true.这样就解决了构造的问题,而在相反的那一端,destructor也需要有条件地施行与mat_identity身上,但只有在mat_identity已经被构造起来时才算数.要判断mat_identity是否被构造出来,很简单, 如果那个临时对象为 true,就表示构造好了

3.对象数组

假设有下列的数组定义:

Point knots[10];

需要完成什么呢?如果Point既没有定义一个constructor也没有定义一个destructor,那么工作不会比建立一个"内建(build-in)类型所组成的数组"更多,也就是说,只需配置足够的内存以储存10个连续的Point元素.
然而Point的确定义了一个 default destructor,所以这个destructor必须轮流施行于每一个元素上.一般而言这是经由一个或多个runtime library函数达成.在cfront中,使用一个被命名为vec_new()的函数,产生出以 class objects 构造而成的数组.新近的编译器则提供两个函数一个用来处理"没有virtual base class"的 class,另一个用来处理"内带virtual base class"的 class.后一个函数通常被称为 vec_vnect.函数类型通常如下:

void *vec_new() {
    void *array,        // 数组起始地址
    size_t elem_size,    // 每一个class object的大小
    int elem_count;        // 数组中的元素数目
    void (*constructor)(void *),
    void (*destruction)(void *, char)
}

其中constructor和destructor参数是这个 class 的default construct和default destructor的函数指针.
参数array带有的若不是具名数组(本例为knots) 的地址,就是0.如果是0,那么数组将经由应用程序的 new 运算符,被动态配置于heap中.
参数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()的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];
// 明确地初始化前3个元素
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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值