C++对象模型笔记:dynamic binding

C++ 对象模型笔记:dynamic binding

    编译器对于多态的实现是怎样的呢?下面请看一个例子:

Class Point

{

Public:

Virtual void print();

……

};

 

Class Point2D : public Point

{

Public:

Virtual void print();

…

};
(实现部分略)

 

Point2D pt2d;

Point *pt = &pt2d;

Pt->print(); //这里的多态要求是要调用Point2D:: print( );

 当然,在这个例子里面,如果你编译的时候用优化选项,编译器也许会把上面三条语句优化如下:Point2D pt2d; pt2d.print( ); !!!你也许会惊讶:编译器这么牛?!是的,编译器会分基本块,然后对每一个基本块进行优化合并;

但是但对于下面的例子,估计再牛的编译器也没有办法:

void printPoint(Point * pt)
{
pt->print();
……
}

//在某处调用:
Point pt;
…
printPoint(&pt);
…

Point2D pt2d;
…
//再另外的某处调用
printPoint(&pt);
可以看到,如果不用点措施,牺牲一点东西,printPoint里面是不知道那个Point指针的所指向的真正对象是哪个的。

 具体实现如下:

    1、编译器遇到了class Point的定义的时候,发现有里面有virtual的成员函数,于是将这个类的定义转换如下:

//c++ 伪代码,实际的编译器是不会这么做的,他会把这些直接转成机器码。

struct Point
{
void *vptr_point; //vptr,指向下面定义的全局变量vtable_Point;
…. //其他数据成员
};

//虚函数Point::print经过name mangling转化后的全局函数
void print_Point(const Point *p){….}

//编译器自动生成的构造函数经name mangling转换过来的全局函数,以确
//保vptr正确初始化
void Point_constructor(Point *p)
{
p->vptr_point = vtable_Point;
}

void * vtable_Point[] = {&print_Point, } //虚函数表

2、对Point2D的转换如下的伪代码所示:(注意,虽然Point2D的定义里面没有定义vritual,但是其基类Point有成员函数定义了virtual,所以还是有虚函数表,即使Point2D什么都没有写,如下所示:class Point2D : public Point{}也有虚函数表,Point2D的每一个对象也还会有vptr成员。 )

//Point2D的伪代码
struct Point2D
{
void *vptr_point;
/*vptr,其实,这个指针是从基类Point里面继承下来的,所以这的名
字不变,还是vptr_point(再次强调,这是伪代码,不要以为实际编译器
里面真的给vptr取了这个名字啊!)指向下面定义的全局变量.*/
…. //其他数据成员
};

//虚函数Point2D::print经过name mangling转化后的全局函数
void print_Point2D(const Point2D *p){….}

//编译器自动生成的构造函数经name mangling转换过来的全局函数,以确保
//vptr正确初始化
void Point2D_constructor(Point2D *p)
{
p->vptr_point2D = vtable_Point2D;
}

//Point2D的虚函数表
void * vtable_Point2D[] = {&print_Point2D };

/* 注意,如果Point2D里面没有定义Print(也就是说派生类没有override 虚函数),那么这个地方的初始化就变成 Void * vtable_Point2D[] = {&print_Point}; */

那么下面的语句:

Point2D pt2d;

Point *p = &pt2d;

p->print();

就会变成类似于下面的伪代码:(C式的,不是 C++ 式的)

struct Point2D pt2d; //再次说明是C式的伪代码,C语言的定义是没有什么构造函数的

Point2D_Constructor(&pt2d); //调用上面提过的由编译器自动生成的构造函数转换过来的全局函数,作用是正确初始化pt2d中的指针,让它指向Point2D的需函数表;

//指针类型转换,看我笔记:指针类型转换;
Point *p = (Point *)&pt2d;

(p->vptr_point)[0] )(pt) //调用p里面的vptr_point[0], 注意上面的初始化中的vptr_point指向Point2D的虚函数表,这个表格的第一项就是放则print相关的入口地址:指向Point2D::print,后面那个pt是把参数(也就是this指针)传进去。

以上的过程都是在编译阶段就做好了的,那些虚函数表在编译阶段就已经做好了。所以对于多态的执行的代价如下:

    1、 对于空间来说,每一个定义了virtual的类,都在全局数据区里面有一张虚函数表,虚函数表的大小决定于这个类的体系(就是这个类及其基类)中虚函数的个数。(这张表是在编译阶段就已经定义好了)

    2、 以上类的每一个对象实例,在空间上多了一个指针的空间。

    3、在类的构造函数里面,多了一条语句的开销(这条语句就是初始化上面多出来的指针,指向相应类型的虚函数表),这个要留意,如果类中没有声明构造函数,这个时候,编译器会自动生成一个(说到这里,不要以为编译器无论在什么时候都会为你的类生成一个默认构造函数啊~,以后的笔记会对这个问题重点讨论),因此,还多了一个你可能并不想要的调用函数的开销(当然,也可能是以内联的方式嵌到代码当中,这个就要看编译器的能力了)

    4、 在执行语句p->print();的时候,由于编译器已经转换为(p->vptr_point)[0] )(pt);实际上多个间接层,看出来没有,一般的p->f()只需要f_Point()…做全局调用就可以了(name mangling转换),现在却要对p寻址,寻址了还要找vptr_point在取它指向的0-4的字节,然后再调用那个地址…。说起来好像间接层不止一个……

    以上的4点就是c++中虚函数调用的运行时候所付出的几乎所有代价。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值