【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;

  那么就和自由第一个元素会被析构。其他的元素仍然存在--虽然其相关的内存已经被要求归还了。

  不要将一个基类指针指向一个继承类对象的数组。

  

posted @ 2015-07-20 08:59  vincently  阅读(435)  评论(0编辑  收藏  举报