深度探索c++对象模型之类对象数组的黑盒

来源:互联网 发布:php大麦户源码程序 编辑:程序博客网 时间:2024/05/23 22:00

      还是以前的Point类,如果我们用Point声明了一个数组,比如【Point p[10];】,在编译器层面会发生什么呢?这要分为两种情况,首先,如果我们在定义Point类时,既没有定义一个constructor,也没有定义一个destructor,那么建立Point类对象的数组和建立一个int类型的数组并没有什么本质区别,只要开辟出10个连续的内存能储存这些Point元素就可以了;但是,如果Point类确实定义了constructor或destructor呢?那么这些构造器或析构器一定要轮流初始化或析构掉每个元素,这些动作,一般要通过runtime library里一个叫vec_new()的函数完成,而在最近的编译器中,如borland、microsoft、sun,干脆直接提供两个孪生函数,一个用来处理没有virtual base class的类,一个用来处理内带virtual base class的类,后者的函数名字叫vec_vnew()。现在,让我们来回头看一下所谓的vec_new()函数长什么样子:

void* vec_new(void* array,      //传递进来的数组首地址size_t elem_size, //数组中每个类对象的大小int elem_count,   //对象中的元素个数void (*constructor)(void*),//请注意!这是编译器的特权!对于我们用户而言void (*destructor)(void*,char) //依然不可以取得构造器或析构器的函数地址!){...}
其中参数列表中的最后两个两个参数,是这个类中的default constructor和default destructor的函数指针。参数array的值要么是具名数组【本例中是p】的地址,要么是NULL【0】,如果是NULL,那么编译器会使用new运算符,将数组内存开辟在堆中【heap】。所以,像我们文章开始处的代码,可能被编译器扩展为下列形式:

Point p[10];vec_new(&p, sizeof(Point), 10, & Point::Point, 0);
      对于支持exception handling的编译器而言,destructor的提供也是必要的,这样当p数组的生命期结束时,就可以调用一个相似的vec_delete()函数,其函数类型也和vec_new()差不多【少了第四个参数】:

void * vec_delete(void *array,size_t elem_size,int elem_count,void (*destructor)(void *,char)){...}
在vec_delete()中,Point类的析构器会轮番作用于每个数组元素之上。

      下面,让我们来改一下文章开始处的代码:

Point p[10] ={Point(),Point(1, 2, 3),-1};
编译器会怎么应付它们呢?首先,编译器会将它们划分为两个部分,一部分是明显获得初值的对象元素,也就是赋值列表中啊前三个,它们将不再需要vec_new();而对于那些没有得到明显初值的元素,则会依旧调用vec_new(),所以,上面的代码经过编译器处理之后:

Point p[10];//明确初始化前三个Point::Point(&p[0]);//其中的&p[0]就是this指针Point::Point(&p[1], 1, 2, 3);Point::Point(&p[2],-1, 0, 0);//然后再调用vec_new初始化后面七个vec_new(&p+3, sizeof(Point), 7, &Point::Point, 0);

      如今走到这里,一切看起来都是那么合情合理。然而在这风平浪静的水面之下,却潜藏这一股难以察觉的暗流。如果您是第一次读此类文章,相信您会和我一样还一脸懵逼。现在,是时候揭开暗流的面纱了,首先我们回顾一下Point类,让我们假设一下,如果Point类的默认构造参数,是要传递默认参数的,比如定义成【Point(int x=0, int y=0)】,那么在我们上面的扩展代码中,是经由一个函数指针调用构造器的,既然是经由函数指针,那如何把这些参数传递过去呢?

      先来看看作者怎么说:经由一个函数指针来激活【startup】constructor,是无法存取default argument values的,接下来是一句【This has always resulted in less that first class handling of the initialization of an array of class objects】。。。首先我们要明白,在C++中,分为第一级类和第二级类,比如int、char等内建类型,它们都能可以在程序运行时创建、可以作为返回值、可以传递、可以赋值等等,所以它们是第一级类【first class】;而像函数这种东西,它就不可以在程序运行时创建,所以它属于第二级类。那么我们用户自己的自定义类类型呢?比如我们的Point,它们也应该被当作first class【第一级类】,也应该和int一样拥有同等地位。所以那句英语的意思【表达意思,非翻译意思】就是:使用函数指针间接调用constructor,却无法传递默认参数【如果有的话】,这种现象不符合自定义类类型作为first class的身份!比如我们可以写【int a[10];】,却不能写【Point p[10];】!可Point与int,是属于同一级别的啊,既然支持了int,要是不支持Point,那多尴尬啊。

      然而这种尴尬的现象(写【Point p[10];】编译不通过)最多只存活到了cfront1.0版本,在这之后的版本里,先是修改了C++的库,后来干脆直接修改了语言本身,大概的策略类似于嵌套调用,cfront内部产生一个stub constructor,然后在这个函数内部再调用用户的有参constructor:

Point::Point() //这个是cfront产生的stub constructor{Point(0, 0); //然后在这里调用程序员自己的constructor,并将参数值明确传递过去,由于constructor地址已取,所以不能做成inline}
当然,上面的stub版本,只有当Point object数组真正产生出来时,它才会产生和调用。

2 0