目录
全局对象(Global Object)
如果我们有以下的程序片段:
Matrix identity;
int main()
{
//identity 必须在此处被初始化
Matrix m1 = identity;
...
return 0;
}
C++保证,一定会在main函数中第一次用到identity之前,把identity构造出来,而在main()函数结束之前把identity摧毁掉。像identity这样所谓global object如果有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并不自动设定初值)。在C语言中一个global object只能够被一个常量表达式(可在编译时期求值的那种)设定值。当然,constructor并不是常量表达式。虽然class object在编译时期可以被放置于data segment中并且内容为0,但是constructor一直要到程序启动(startup)时才会实施。必须对一个“放置于program data segment中的object的初始化表达式”做评估(evaluate),这正是为什么一个object需要静态初始化的原因。
当cfront还是唯一的C++编译器,而且跨平台移植性比效率的考虑更重要的时候,有一个可移植但成本颇高的静态初始化(以及内存释放)方法,我把它称作munch。下面是munch策略:
1)为每一个需要静态初始化的文件产生一个_sti()函数,内含必要的constructor调用操作。例如前面所说的identify对象在matrix.c中产生出下面的_sti()函数(译注:我想sti就是static initialization的缩写):
__sti__matrix_c__identity(){
//C++ 伪代码
identity.Matrix::Matrix();//译注:这就是static initialization
}
其中matrix_c是文件名编码,_identity表示文件中所定义的第一个static object。在__sti之后附加上这两个名称,可以为可执行文件提供一个独一无二的标志符号。
2)类似情况,在每一个需要静态的内存释放操作(static dealllocation)的文件中,产生一个__std()函数(译注:我想std就是static deallocation的缩写),内含必要的destructor调用操作,或是其inline expansions。在我们的例子中会有一个__std()函数被产生出来,针对identify对象调用Matrix destructor。
3)提供一组runtime library “munch”函数:一个_main()函数(用以调用可执行文件中的所有__sti()函数),以及一个exit()函数(以类似的方式调用所有的__std()函数)。
局部静态对象(Local Static Object)
假设我们有以下程序片段:
const Matrix&
identity()
{
static Matrix mat_identify;
//...
return mat_identify;
}
Local static class object保证了什么样的语义?
- mat_identify的constructor必须只能实施一次,虽然上述函数可能会被调用多次。
- mat_identify的destructor必须只能实行一次,虽然上述函数可能会被调用多次。
编译器的策略之一就是,无条件地在程序起始(startup)时构造出对象来。然而这会导致所有的local static objects都在程序起始时初始化,即使它们所在的那个函数从不曾被调用过。因此,只在identify()被调用时才把mat_identify构造起来,是比较好的做法。我们应该怎么做呢?
首先,导入一个临时性对象以保护mat_identity的初始化操作。第一次处理identity()时候,这个临时对象被评估成false,于是constructor会被调用,然后临时对象被改成true。这样解决了构造的问题。相反的另一端,destructor也需要施加到mat_identity身上,但只有在mat_identity已经构造出来才算数。如果那个临时对象是true,就表示构造好了。下面是cfront的输出。
//被产生出来的临时对象,作为戒护作用
static struct Matrix *__0__F3 = 0;
//C++的reference在C中是以pointer来代替的
//identity() 的名称被mangled
struct Matrix*
identity__Fv()
{
static struct Matrix __lmat_identity;
//如果临时性的对象已被设立,那就什么也别做。否则
//(a)调用constructor:__ct__6MatrixFv;
//(b)设定保护对象,使它指向目标对象
__0__F3
?0
:(__ct__6MatrixFv( &__lmat_identity),
(__0__f3 = (&__lmat_identity)));
}
最后,destructor必须在“与text program file有关联的静态内存释放函数(static deallocation function)”中被无条件的调用:
char __std__stat_0_c_j()
{
__0__F3
? __dt__6MatrixFv(__0__F3, 2)
:0;
...
}
对象数组
假设我们有以下的数组定义:
Point knots[10];
什么东西需要完成?如果Point既没有定义constructor也没有定义一个destructor,那么我们的工作不会比建立一个“build-in类型所组成的数组”更多,也就是说我们只要配置足够内存以存储10个连续的Point元素即可。
然而Point的确定义了一个default destructor,所以这个destructor必须轮流实行于每个元素之上。一般而言这是经由一个或者多个runtime library函数达成的。在cfront中,我们使用一个被命名程vec_new()的函数,产生出以class objects构造而成的数组。比较新的编译器,包括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 *),
)
其中的constructor和destructor参数是这一class之default constructor和default destructor的函数指针。参数array持有的若不是具名数组(本例为knots)的地址,就是0。如果是0,那么数组将由应用程序的new运算符,被动态配置于heap中。Sun把“由class objects所组成的具名数组”和“动态配置而来的数组”的处理操作分成两个library函数:_vector_new2和_vector_con,他们各自拥有一个virtual base class函数实现。
在vec_new()中,constructor实行于elem_count个元素之上。对于支持exception handling的编译器而言,destructor的提供是必要的。下面是编译器可能针对我们10个Point元素所做的vec_new()调用操作:
Point knots[10];
vec_new( &knots, sizeof(Point), 10, &Point::Point, 0);
如果程序员提供一个或多个明显初值给一个由class objetcs组成的数组,像下面这样,会如何:
Point knots[10] = {
Point(),
Point(1.0,1.0,0.5),
-1.0
};
对于那些明显获得初值的元素,vec_new()不在必要。上面的定义可能转换为下面的代码。
Point knots[10];
//C++伪代码
//显示初始化前3个元素
Point::Point(&knots[0]);
Point::Point(&knots[1],1.0,1.0,0.5);
Point::Point(&knots[2],-1.0,0,0);
//以vec_new初始化后7个元素
vec_new(&knots[0]+3, sizeof(Point), 7, &Point::Point, 0);