首页 > 代码库 > 执行期语意学

执行期语意学

执行期语义主要从一下两个方面展开:

1 执行期发生的一些转换。
2 临时性对象。
 

执行期发生的转换

一 对象的构造和解构(构造和析构必须调用时)

1 一般而言,constructor和destructor的安插都如你锁预期。对象定义时构造函数被调用,初始化该对象;区段结束(离开点)时,destructor被调用
2 如果一个区段(以{}括起来的区域)或函数中有一个以上的离开点destructor必须被放在每一个离开点
3 一般而言,我们应该把object尽可能放置在使用它的那个程序区段附近,这样做可以节省不必要的对象产生操作和摧毁操作。

全局对象

我们有如下程序片段:
Matrix identity;
main()
{
    Matrix m1=identity;
    return 0;
}
1 c++保证,一定会在main()函数中第一次调用identity之前,把identity构造出来,而在main()结束之前把identity摧毁掉。 我们说全局identity需要静态的初始化操作和内存释放操作
2 静态初始化的原因?
    在c语言中一个全局对象只能被一个常量表达式(可在编译时期求其值的那种)设定初值。而constructor并不是常量表达式
    因此,虽然class object在编译时期可以放置在data segment并且内容为0(c++会这样做,而c这不处理),但constructor一直到程序激活(startup)时才会实施,必须对一个“放置在program data segment 中的object的初始化表达式”做评估。
3 cfront的实现策略是munch策略。会产生_sti()和_std()函数,以及一组运行时库,一个_main()函数,一个_exit()函数。

局部静态对象

我们有如下程序片段
const Matrix & identity(){
    static Matrix mat_identity;
    return mat_identity;
}
local static class object保证了如下意义:
1 mat_identity的constructor必须只能执行一次,虽然上述函数可能会被调用多次。
mat_identity的destructor必须只能执行一次,虽然上述函数可能会被调用多次。
3 实现方法:1> 无条件地在程序起始(startup)时构造出对象来。
            2> 导入一个临时对象保护mat_identity的初始化操作。第一次处理时该对象评估为false。然后constructor会调用,然后被改为true。

对象数组

我们有下列数组定义:
Point knots[10];
1 如果Point既没有顶一个constructor也没有定义一个destructor,我们只需配置足够的内存以存储10个连续的Point元素
2 如果Point的确定义了一个default constructor,所以这个constructor必须轮流实施于每个元素之上。一般这是经由一个或多个运行时库达成的。如cfront中的vec_new();
3 如果Point也定义了一个destructor,当knots的声明结束时,该destructor也必须实施于那10个元素身上。运行时库可能是vec_delete()。

default constructor和数组

1 vec_new()取一个default constructor的地址,激活constructor,然而这样将无法(不能允许)存取default argument values
2 cfront所采用的方法是产生一个内部的sub construtor,没有参数。在其函数内调用由程序员提供的constructor,并将default 参数值明确地指定过去。

new和delete运算符

int *pi=new int(5);
1 new调用其实有两个步骤来完成的
1> int *pi=__new(sizeof(int)); //通过适当的new运算符实体配置内存;
2> *pi=5;//然后设置初值。
注意:初始化操作应该在内存配置成功后才能执行。
2 delete的情况类似
delete pi;//可能转化成一下步骤
if(pi!=0)
    __delete(pi);
 
以constructor来配置一个class object,情况类似。例如:
Point3d *origin=new Point3d;//被转化为
3 Point3d *origin;
  if(origin=__new(sizeof(Point3d)))
       Point3d::Point3d(origin);
4 destructor的应用类似
delete origin;//被转化为
if(orgin!=0){
    Point3d::~Point3d(origin);
    __delete(orgin);
}
 

针对数组的new语意

1 int *p_array=new int[5];
    vec_new()不会调用,因为它的主要功能是把default constructor施行于class object所组成数组的每一个元素身上。不过new运算符函数会被调用。
2 //struct simple{int i1,i2;};
  simple *p_aggr=new simple_aggr[5];
    vec_new也不会被调用。因为:simple并没有定义一个constructor和destructor,所以配置数组以及清除p_aggr数组的操作,只是单纯地获取内存和释放内存而已
3 如果class定义有一个default constructor,某些版本的vec_new()就会被调用,配置并构造class objectes所组成的数组
Point3d *p_array=new Point3d[10];//通常会被编译为
Point3d *p_array;
p_array=vec_new(0,sizeof(Point3d),10,&Point3d::Point3d,&Point3d::~Point3d);

临时性对象

    理论上,c++标准允许编译器厂商对是否产生临时性对象有完全的自由度。但实际上,由于市场竞争,几乎保证任何表达式如果有这种形式
1 T c=a+b;
 加法定义为:T operator+(const T &,const T &); 或T T::operator(const T&);
 实现根本不会产生一个临时对象
注:1> 直接以拷贝构造的方式,将a+b的值放到c中。
    2> 视operator的定义而定,NRV优化也可能实施起来,这将导致直接在上述c对象中求表达式结果,避免执行copy constructor和具名对象的构造和析构。
2 然而,意义相当的赋值叙述句:
 c=a+b;
不能忽略临时对象,相反,他会导致下面的结果:
//c++伪码
T temp;
temp.operator+(a,b); 
c.operator=(temp);
temp.T::~T();
3 没有出现目标对象:a+b; 
    这时有必要产生一个临时对象,以放置运算后的结果。然后其析构有点复杂:
C++标准上这么规定:
    临时性对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤。该完整表达式造成临时性对象的产生。完整表达式就是被涵括的表达式最外围那个。
4 临时性对象声明规则有两个例外:
1> 表达式被用来初始化一个object时:凡含有表达式执行结果的临时对象,应该存留到object的初始化操作完成为止
2> 如果一个临时对象被绑定与一个reference,对象将残留,直到被初始化之reference的生命结束,或直到临时对象的声明范围结束(视哪一种情况先到而定)。