目录
执行期语义主要从一下两个方面展开:
- 执行期发生的一些转换。
- 临时性对象。
执行期发生的转换
一 、对象的构造和解构(构造和析构必须调用时)
- 一般而言,constructor和destructor的安插都如你所预期。对象定义时构造函数被调用,初始化该对象;区段结束(离开点)时,destructor被调用。
- 如果一个区段(以{}括起来的区域)或函数中有一个以上的离开点,destructor 必须被放在 对象被生成后与 每一个离开点前。
- 一般而言,我们应该把object尽可能放置在使用它的那个程序区段附近,这样做可以节省不必要的对象产生操作和摧毁操作。
全局对象
我们有如下程序片段:
Matrix identity;
main()
{
Matrix m1=identity;
return 0;
}
- c++保证,一定会在main()函数中第一次调用identity之前,把identity构造出来,而在main()结束之前把identity摧毁掉。 如果这种 global object 定义了构造与析构函数,我们说它需要静态的初始化操作和内存释放操作。
- 静态初始化的原因?
在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保证了如下意义:
- mat_identity的constructor必须只能执行一次,虽然上述函数可能会被调用多次。
- mat_identity的destructor必须只能执行一次,虽然上述函数可能会被调用多次。
- 实现方法:1> 无条件地在程序起始(startup)时构造出对象来,然而会导致所有的局部静态对象被初始化,即使所在函数未被调用。
2> 导入一个临时对象保护mat_identity的初始化操作。第一次处理时该对象评估为false,constructor会被调用,然后被改为true,之后不再调用。
对象数组
我们有下列数组定义:
Point knots[10];
- 如果Point既没有顶一个constructor也没有定义一个destructor,我们只需配置足够的内存以存储10个连续的Point元素。
- 如果Point的确定义了一个default constructor,所以这个constructor必须轮流实施于每个元素之上。一般这是经由一个或多个 runtime library 函数达成的。如cfront中的 vec_new();
- 如果Point也定义了一个destructor,当knots的声明结束时,该destructor也必须实施于那10个元素身上。runtime library 可能是vec_delete()。
default constructor和数组
- vec_new()取一个default constructor的地址,激活constructor,然而这样将无法(不能允许)存取default argument values。
- 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;//被转化为
Point3d *origin;
if(origin=__new(sizeof(Point3d)))
Point3d::Point3d(origin);
//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);
为什么vec_new中会有 destructor?在个别元素的构造过程中,如果发生 exception ,destructor 就会被传递给 vec_new,vec_new有责任在 exception 发生时将那些内存释放掉。
Placement Operator new 语意
有一个预先定义好的重载的 new 运算符,称为 Placement Operator new。它需要一个 void* 指针作为参数,指示出要生成的对象的存放的内存地址。调用如下:
Point2w *ptw = new (arena) Point2w;
arena 指向内存中的一个区块,用以放置新生成的对象。
//C++伪码
Point2w *ptw = ( Point2w* ) arena; //指定地址
if( ptw != 0 )
ptw->Point2w::Point2w(); //调用构造函数
析构后希望再次使用 arena 的正确方法:
p2w->~Point2w;
p2w = new ( arena ) Point2w;
如果直接使用 delete ,对象被析构的同时指向的内存也会被释放,之后不能再使用了。
一般而言,placement new operator 并不支持多态,被交给 new的指针,应当适当的指向一块预先配置好的内存。
三、临时性对象
理论上,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++标准上这么规定:
临时性对象的被摧毁,应该是对完整表达式求值过程中的最后一个步骤。该完整表达式造成临时性对象的产生。完整表达式就是被涵括的表达式最外围那个。例如
((objA > 1024) && (objB > 1024))
? objA + objB : foo(objA ,objB)
一共有五个子算式,内含在一个“?:完整表达式”中。任何一个子式所产生的任何一个临时对象,都应该在完整表达式被求值完成后,才可以销毁。
4 临时性对象声明规则有两个例外:
1> 完整表达式被用来初始化一个 object 时:凡含有表达式执行结果的临时对象,应该存留到object的初始化操作完成为止。
2> 如果一个临时对象被绑定与一个reference,对象将残留,直到被初始化之reference的生命结束,或直到临时对象的声明范围结束(视哪一种情况先到而定)。
转载自:botou 的博客园