执行期语义学 RunTime Semantics
if( yy == xx.getValue() ) …
X xx; Y yy;
class Y{
public:
Y(); ~Y(); bool operator == (constY& )const;
};
class X{
public:
X(); ~X(); operator Y()const; //重载转换类型操作符 必须成员不能有参数不能有返回值详细在 http://www.cppblog.com/zqsand/archive/2010/03/15/109748.html里面有介绍
X getValue();
};
看看上面的表达式怎么执行的~~~
首先等号运算符的参数确定 if(yy.operator==(xx.getValue()));
Y的== 需要一个Y型的参数,但是getvalue得到的是一个X型的,如果没有X到Y的转型方法,这个式子是错的~ 而恰好X有个类型转换符~
if(yy.operator == (xx.getValue().operator Y()))
增加的代码都是编译器默默为我们加上的~~~
注意在这个过程中,我们需要一个临时的Xobject 储存 getValue返回值 X temp1 = xx.getValue()
一个class Y object 储存 operator Y()返回值 Y temp2 = temp1.operator Y();
一个 int 放置 等号返回值 int tmp3 = yy.operator == (temp2);
最后析构函数会实施在每一个临时class object上.
所以,我们的代码变成:
{
X temp1 = xx.getValue();Y temp2 = temp1.operator Y();int tmp3 = yy.operator == (temp2);
if(tmp3) dosth```
tmp2.Y::~Y();
tmp1.X::~X();
}
surprise~~-----------------------------------------------------------------------
6.1-对象的构造和析构
·一般来说,dtor会被放在每一个离开点(object 还存活)之前 ,所以,如果一个区段或者函数有多个离开点,那么每一个return 离开点都要插入一个dtor了。因此我们一般尽量放置object在使用它的程序区段附近,节省不必要的对象产生与摧毁操作。
·全局对象:全局对象如果有ctor或者dtor的话需要静态的初始化操作和内存释放操作。c++中全局对象放在data segment中,如果不明确指定值,内存内容为0.
但是ctor必须等到程序startup后才能实施。由于必须对一个放在datasegment 中的object初始化表达式evaluate ,所以object需要静态初始化
一种策略(cfont 的 munch)
为每一个需要静态初始化的档案产生一个 _sti()函数,内带必要的ctor调用操作或者inline expansions。 类似的产涩会给你一个std()函数调用dtor
一个_main()函数调用sti 一个 exit()函数调用_std()
然后cfont在我们的程序中安插对 _main _exit 的调用。 最后需要解决的是如何收集各个对象的sti和std。cfont使用了nm命令 , 打印出符号表格(目标文件的符号表),然后munch会搜索所有用sti或者std开头的目标函数,把他们记录到一个表格,当main和exit调用时候便利表格即可。
修改版本的方法是:system V中,coff格式的目标文件,检验可执行文件,找出有着_linknodes并且内带一个指针指向 sti 和std函数的文件,把他们都串联起来,接下来把链表头结点设置为一个全局的_head object (定义在新的 patch runtime library),这个library中有一种不同的_main _exit 他们会以head为起点,遍历链表,调用sti和std。
实际上现在的ELF格式,有init 和.fini两个section,完成静态初始化和释放操作。编译器设定的startup函数会完成平台特定的支持
virtual base class 的指针或者引用存取virtual base class subobject,是一种只有在执行期才能加以确定的操作。所以,编译器需要支持class object 的静态初始化,至少涵盖object的指针和reference。
局部静态对象
const Matrix& identity(){
static Matrix mat_identity;
return mat_identity;
}
mat_identity的ctor必须只执行一次,mat_identity的dtor必须只执行一次
编译器只在identity被调用的时候才构造mat_identity,这样避免如果不被调用也需要构造所有对象。同时编译器引入条件式解析~也就是如果构造了则解析之
对象数组:
Points knots[10];
如果Points没有定义ctor和dtor只要分配空间即可
如果有default ctor ,ctor必须实施于每个元素身上~这是由runtime library 完成的。 cfont中 我们使用vec_new()函数 MS和Sun提供两个函数,一个用来处理 vbs的class 一个处理内带base class 的class,后者为 vec_vnew() 函数原型基本如下
void* vec_new(void *array,size_t elem_size,int elem_count,void (*ctor)(void*),void(*dtor)(void*,char)))
array如果是0,数组由new分配与heap, vec_new(&knots,sizeof(Point),10,&Point::Point,0);
6.2 new 和 delete 运算符
int *pi = new int(5);
执行步骤:
int* pi = __new (sizeof(int));
*pi = 5;
int *pi;
if(pi = __new(sizeof(int)))
*pi=5;
delete pi;
if(pi!=0)
__delete (pi);
注意pi并不会自动清除为0!
CTOR
Point3d * origin=new Point3d;
if(origin = __new(sizeof(Point3d))){
try{
origin = Point3d::Point3d(origin);
}
calch(…){
__delete(origin);
throw;//上传exception
}
}
DTOR
delete origin;
if(origin!=0){
Point3d::~Point3d(origin);
__delete(origin);
}
一种library对new的设计:~~
extern void* operator new(size_t size){
if(size==0)size=1;
void *last_alloc;
while(!(last_alloc=malloc(size))){
if(_new_handler) (*_new_handler)();
else return 0;
}
return last_alloc;
}
虽然new T[0];是合法的,但是语言要求每次对new的调用必须返回一个独一无二的指针,解决该问题的传统方法是传回一个指针,指向默认为1byte的内存区块。所以size被设为1.然后这种设计允许使用者提供一个属于自己的_new_handler() 函数。
extern void operator delete (void *ptr) { if(ptr)free (char*)ptr;}
针对数组的new 语义:
int *p_array = new int[5];
这时候 vec_new()不会真正调用,因为,它的主要功能是把default ctor 实施于class object数组的每个元素身上。new运算符会被调用:
int *p_array = (int*) __new(5*sizeof(int));
如果数组的class object 有default ctor vec_new才会被调用。
Point3d *p_array = new Point3d[10];编译成:
Point3d *p_array = vec_new(0,sizeof(Point3d),10,&point3d::Point3d,&Point3d::~Point3d);
个别数组构造如果exception发生,dtor被传输给vec_new ,已经构造的object需要dtor 实施。
delete 时候,开始需要程序员指定大小,后来编译器开始不适用程序员指定的,而是只需要写delete [] ptr 即可。
如何记录数组大小呢:
一种方法在vecnew返回的内存块配置一个额外的word,大小放在其中。
如果
class Point {public:virtual ~Point (){}};
class Point3d : public Point {public:virtual ~Point3d(){}};
如果Point *ptr = new Point3d[10];
当我们delete [] ptr;时候只有 Point::~Point被掉用````
在vc里敲了代码验证确实如此~~~
实施于数组上的dtor是根据交给vec_delete()函数的被删除指针类型的dtor,也就是point的dtor,每一个元素大小也被一起传了过去。
如何避免:
避免一个base class 指针指向一个derived class 的数组。如果真的要这么写看代码吧
class point{
public:
int p;
point(){cout<<"point ctor"<<endl;}
~point(){cout<<"point dtor"<<endl;}
};
class point3d:public point{
public:
int q;
point3d(){cout<<"point3d ctor"<<endl;}
~point3d(){cout<<"point3d dtor"<<endl;}
};
int main()
{
point *ptr = new point3d[3];
//delete [] ptr; 这样写是不行的
//要这样写
for(int i=0;i<3;i++){
point3d * p=&((point3d*)ptr)[i]; //恢复成point3d数组指针
delete p;
}
}
Placement Operator New
有一个重载过的new 运算符 需要两个参数,类型为void*
Point2w *ptw = new(area) Point2w;
其中area指向内存一个区块,用来放置产生出来的Point2w object.这个预先定义好的placement operator new 实现方法: 将获得的指针arena 所指的地址传回即可
void* operator new (size_t,void* p) {return p;}
事实上,他执行的另一半工作是:把point2w 的ctor 实施于 arena所指的地址上
Point2w *ptw = (Point2w *)arena; if(ptw!=0)ptw->Point2w::Point2w();
-------
p2w->~Point2w;
p2w = new(arena)Point2w;
如果我们用
delete p2w; p2w = new(arena) Point2w;
delete会释放p2w指向的内存 由于下一指令还要用到p2w,我们应该调用dtor并保留存储空间,以便再次使用.
还有一些关于placement opeator new 的设计问题··没看明白 不记了··
6.3临时对象 :
c++对临时对象并无硬性规定,由编译器抉择。
实际上 T c = a+ b; T operator + (const T& ,const T&);
a+b可以直接构建于c上
那么根本不产生临时对象
但是,意义相当的 c=a+b;不能忽略临时对象":
T temp; temp=operator+(a,b);c.operator =(tmp);tmp.T::~T();
注意c=a+b;中,直接传递c进入operator 中,也就是不要tmp的话:由于operator函数不为其外加参数调用dtor(期望一个新鲜的内存),所以必须在其调用前调用dtor.然而转换操作会变成c.T::~T();c.T::T(a+b);copy ctor dtor copy assignment operator 都可以自定义,所以我们用 析构和拷贝构造代替赋值一般而言是不安全的,所以需要临时对象调用operator=
所以 T c=a+b;比 c=a+b;更有效率
临时对象生命周期:
临时对象被摧毁,应该是对完整表达式求职过程的最后一个步骤,该完整表达式造成临时对象的产生
如果一个临时对象绑定在一个reference上,对象将残留,知道被初始化之reference生命结束,或者知道临时对象的声明范畴结束。