【C++】深度探索C++对象模型读书笔记--执行期语意学(Runtime Semantics)
对象的构造和析构:
全局对象
C++程序中所有的global objects都被放置在程序的data segment中。如果显式指定给它一个值,此object将以此值为初值。否则object所配置到的内容为0。
如果全局对象如果有构造函数或析构函数的话,我们说它需要静态的初始化操作和内存释放操作。编译器的执行步骤如下:
1.为每一个需要静态初始化的文件产生一个_sti()函数,内含必要的构造函数调用操作。
2. 在每一个需要静态的内存释放操作的文件中,产生一个_std()函数,内含必要的析构操作。
3.在main函数中安插_main()函数(用以调用执行文件中所有的_sti()函数),以及一个exit()函数(用以调用所有的_std()函数)。
局部静态对象(Local Static Objects)
假设我们有一下程序片段:
const Matrix& identity() { static Matrix max_identity; //... return mat_identity; }
虽然函数会被调用很多次,但是max_identity的构造函数和析构函数只能被调用一次,即局部静态对象只进行一次初始化。
为了保证这个语意。编译器加入一个临时对象来保护mat_identity的初始化操作。第一次处理identity()时,这个临时性对象呗评估为false,于是constructor会被调用,然后临时性对象改为true。这样就解决了构造的问题。而在相反的那一端,destructor也需要有条件地施行于mat_identity身上,但只有在mat_identity已经被构造起来才算数。要判断mat_identity是否被构造出来,很简单,如果那个临时性对象为true,就表示构造好了。困难的是,由于cfront产生C码,mat_identity对函数而言仍然是local(作用域在函数内),因此没有办法在静态内存释放函数(用于全局静态对象的析构)中存取它。因此使用local object地址。下面是cfront的输出:
//被产生出来的临时对象 static struct Matrix *_0_F3 = 0; //C++的reference在c中是以pointer来代替,identity()的名称会被mangled struct Matrix* identity_Fv() { static struct Matrix _1mat_identity; //如果临时性的保护对象已被成立,那就什么也不做。否则 //(a)调用constructor:_ct_6MatrixFv //(b)设定保护对象,使它指向目标 _0_F3 ? 0 : (_ct_6MatrixFv(_1mat_identity), (_0_F3 = (&_1mat_identity))); }
最后,destructor必须在“与text program file”(也就是本例中的stat_0.c)有关联的静态内存释放函数中被有条件地使用:
char _std_stat_0_c_j() { _0_F3 ? _dt_6MatrixFv(_0_F3,2) : 0; }
对象数组:
假设我们有下列数组定义:
Point knots[10];
如果Point既没有定义一个constructor,也没有定义一个destructor,那么我们的工作不会比建立一个“内建(built-in)类型所组成的数组”更多,也就是说我们只需要配置足够内存以存储10个连续的Point元素即可。
如果Point的确定义了一个default constructor,所以这个desturctor必须轮流试行于每一个元素之上。一般而言这是经由一个或多个runtime library函数达成的。
如果Point也定义了一个destructor,当knots生命结束时,该destructor也必须试行于那10个Point元素身上。、
如果程序员提供一个或多个明显初值给一个由class objects组成的数组,像下面这样,会如何:
Point knots[10] = { Point(); Point(1.0, 1.0, 0.5), -1.0 };
对于那些明显获得初值的元素,编译器无需做初始化操作。对于那些没有初始化的对象,编译器仍然需要调用库函数进行初始化。也就是说上面这个定义的后7个对象被编译器产生的代码初始化。
new和delete运算符:
运算符new的运用,看起来似乎是一个单一运算,像这样:
int *pi = new int(5);
但事实上它是由两个步骤完成的:
1. 通过适当的new运算符函数实例,配置所需的内存:
//调用库函数中的new运算符 int *pi = _new(sizeof(int));
2. 将配置而来的对象设立初值:
*pi = 5;
针对数组的new语意:
如果我们定义了一个数组:
Point *p_array = new Point3d[array_size];
则在删除的时候必须调用delete []p_array。因为只有中括号出现时,编译器才寻找数组的维度,否则它便假设只有单独一个objects要被删除。如果程序员没有提供必须的中括号,像这样:
delete p_array;
那么就和自由第一个元素会被析构。其他的元素仍然存在--虽然其相关的内存已经被要求归还了。
不要将一个基类指针指向一个继承类对象的数组。