一个简单的例子
class Y { public: bool operator==(const Y&) const; }; class X { public: operator Y() const; X getValue(); }; X xx; Y yy; if(yy==xx.getValue()) //会发生下列转换 X temp1=xx.getValue(); Y temp2=temp1.operator Y(); int temp3=yy.operator==(temp2); if(temp3) //... temp2.Y::~Y(); temp1.X::~X();
一、对象的构造和析构
一般而言,我们将object尽可能放置在使用它的那个程序区段附近,这样可以节省非必要的对象的构造和析构成本。
destructor必须被放在每一个离开点(当时object还活着)之前。
全局对象
已经初始化全局对象均存储在data segment(数据段),未初始化的全局变量存储在BSS(block started by symbol),C++中如果全局对象没有显式初始化,那么该对象所配置到的内存内容为0,但是构造函数一直到程序启动才会实施。
全局对象如果有constructor和destructor的话,那么他需要静态的初始化操作和释放内存操作。
C语言中的一个全局对象只能够被一个常量表达式(可在编译时期求值的那种)设定初始值。
munch方法:全局变量静态初始化的方法
- 为每一个需要静态初始化的文件产生一个__sti()函数(sti: static initialization),内含必要的constructor调用操作或者inline expansions。
- 为每一个需要静态的内存释放操作产生__std()函数(std: static deallocation),内含必要的constructor调用操作或者inline expansions。
- 提供一组running library”munch”函数:调用所有的__sti()一个_main()函数以及一个调用__std()函数的exit()函数。
Matrix identity1, identity2; main() { Matrix m1 = identity, m2 = identity2; ... return 0; } //对应much策略如下 main() { _main(); //_main()调用__sti_identity1()以及__sti_identity2();对所有的global做static initialization操作 ... exit(); //exit()调用__std_identity1()以及__std_identity2();对所有的global做static deallocation操作 return 0; }
使用静态初始化object会有一些缺点,比如如果支持exception handling,那些objects将不能被置于try区间之内,这对于静态被调用的constructor可能是无法接受的。
静态局部对象
const Matrix& identity() { static Matrix mat_identity; return mat_identity; }
- mat_identity的构造函数只执行一次,即使identity函数调用多次。
- mat_identity的析构函数也只执行一次,即使identity函数调用多次。
编译器的策略是无条件在程序起始处构造对象,这会导致所有的静态对象在程序起始时都初始化,即使调用它们的函数从来没有被调用过。
解决办法是:取出local object地址(由于object是static,其地址在downstream component中将会被转到程序用来放置global object的data segment中)
新的规则要求编译单位中的局部静态对象必须被摧毁,以构造相反的顺序摧毁。由于这些object都是第一次需要时才被构造(如每个含有static local class objects的函数第一次被进入时)所以编译器无法预期集合以及顺序,为了支持新规则,可能需要被产生出来的static class object保持一个执行期链表。
对象数组
Point knots[10];
假如Point定义了一个默认构造函数,在从cfront的策略中,产生一个vec_new
函数(如果含有virtual base class,产生vec_vnew()
函数)(有构造函数时才会生效),用来构造数组:
void vec_new( void* array, //持有的若不是具名数组的地址,就是0,如果是0数组将由应用程序的new运算符被动态配置于heap中 size_t elem_size, //数组中每一个元素的大小 int elem_count, //数组中元素的个数 void (*constructor)(void*), //形参个数为0的默认构造函数指针 void (*destructor)(void*, char) //析构函数指针 )
上树的转化为
Point knots[10]; vec_new(&knots, sizeof(Point), 10, Point::Point, 0);
同样地,cfront的策略中,产生一个vec_delete
函数(或是一个vec_vdelete(),如果class含有virtual base class的话
),用来析构数组
void vec_delete( void* array, //数组的起始地址 size_t elem_size, //数组中每一个元素的大小 int elem_count, //数组中元素的个数 void (*destructor)(void*, char) //析构函数指针 )
如果程序提供一个或多个明显初始值给一个由class objects组成的数组,vec_new()不再有必要。
Default Constructor和数组
如果一个类的构造函数有一个或一个以上的默认参数值,例如:
class complex{ complex(double = 0.0, double = 0.0); }
那么当我们写下complex array[10];
时,编译器最终需要调用
vec_new(&array, sizeof(complex), 10, &complex::complex, 0);
这里的&complex::complex
需要是无参的构造函数,那么应该怎么做?做法是:在&complex::complex
中调用我们自己提供的的constructor,并将default参数值显式指定过去,例如:
complex::complex() { complex(0.0, 0.0); //调用我们自己的构造函数 }