如果有以下程序片段:
Matrix identity; main() { //identity必须在此处被初始化 Matrix m1 = identity; return 0; }C++保证,一定会在main()函数中第一次用到identity之前,把identity构造出来,而在main()函数结束之前把identity摧毁掉。像identity这样的所谓的global obgect如果有constructor和destructor的话,我们就说它需要静态的初始化操作和内存释放操作。C++程序中所有的global objects都被放置在程序的data segment中.如果明确指定给它一个值,object将以该值为初值.否则object所配置到的内存内容为0.因此在下面这段码中:
int v1 = 1024; int v2; v1和v2都被配置于程序的data segment,v1值为1024,v2值为0(这和C略有不同,C并不自动设定初值)。虽然 class object在编译时期可以被放置于data segment中并且内容为0,但constructor一直要到程序激活(startup)时才会实施.必须对一个"放置于program data segment中的object的初始化表达式"做评估,这正是为什么一个object需要静态初始化的原因。下面是静态初始化方法:
1.为每一个需要静态初始化的档案产生一个_sti()函数,内带必要的constructor调用操作或 inline expansions.例如前面所说的identity对象会在matrix.c中产生出下面的_sti()函数(sti可能是 static initialization的缩写):
__sti__matrix_C__identity() { identity.Matrix::Matrix(); // 这就是 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()函数).假设有以下程序片段:
const Matrix &identity() { static Matrix mat_identity; // ... return mat_identity; }Local static class object保证了什么样的语意? mat_identity的constructor必须只能施行一次,虽然上述函数可能会被调用多次. mat_identity的destructor必须只能施行一次,虽然上述函数可能会被调用多次. 编译器的策略之一就是,无条件地在程序起始(startup)时构造出对象,然而这会导致所有的 local static class objects都在程序起始时被初始化,即使它们所在的那个函数从不曾被调用过.因此,只在identity()被调用时才把mat_identity构造出来,是比较好的做法. 首先导入一个临时性对象以保护mat_identity的初始化操作.第一次处理identity()时,这个临时对象被评估为 false,于是constructor会被调用,然后临时对象被改为 true.这样就解决了构造的问题,而在相反的那一端,destructor也需要有条件地施行与mat_identity身上,但只有在mat_identity已经被构造起来时才算数.要判断mat_identity是否被构造出来,很简单, 如果那个临时对象为 true,就表示构造好了.假设有下列的数组定义:
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);运算符new的使用,看起来似乎是个单一运算,比如
int *pi=new int(5);
但事实上它是由两个步骤完成的:
1.通过适当的new运算符函数实例,配置所需的内存:
//调用函数库中的new运算符
int *pi=__new(sizeof(int));
2.将配置得来的对象设立初值:
*pi=5;
delete运算符的情况类似,delete pi;
相当于是:
if(pi!=0)
__delete(pi);
注意:在C++中,new运算符实际上总是以标准的C malloc()完成,虽然并没有规定一定得这么做不可。相同情况下,delete运算符也总是以标准的C free()完成。
placement new:只是operator new重载的一个版本。它并不分配内存,只是返回指向已经分配好的某段内存的一个指针。因此不能删除它,但需要调用对象的析构函数。 如果你想在已经分配的内存中创建一个对象,使用new时行不通的。也就是说placement new允许你在一个已经分配好的内存中(栈或者堆中)构造一个新的对象。原型中void* p实际上就是指向一个已经分配好的内存缓冲区的的首地址。
调用方式如下:
Point2w *ptw=new(arena)Point2w;
函数形式是:
void *operator new(size_t,void *p) { return p; } 事实上这只是所发生操作的一半而已。另外一半无法由程序员产生出来。Placement new operator所扩充的另一半是将Point2w constructor自动实施于arena所指的地址上:
Point2w *ptw = (Point2w*)arena; if (ptw != 0) ptw->Point2w::Point2w(); 这正是placement operator new牛逼的地方,这段代码决定objects被放置在哪里;编译器保证object的constructor会施行于其上。注意下面这个问题:
void fooBar() { Point2w *p2w = new(arena)Point2w; p2w = new(arena)Point2w; } 如果placement operator在原已存在的一个object上构造新的object,而该既存的object有destructor,这个destructor并不会被调用。调用gaidestructor的方法之一是将那个指针delete掉。不过这是个严重的错误:delete p2w;
p2w=new(arena)Point2w;
如上操作,delete运算符会发生作用,这是我们所期待的。但是它也会释放由p2w所指的内存,这却不是我们所希望的,因为后面那条语句马上就会用到p2w了。因此,正确的方法是显示地调用destructor并保留存储空间以便再次使用。
p2w->~Point2w;
p2w=new(arena)Point2w;
