掀起你的盖头来——谈VC++对象模型
发布时间:2006-02-28 12:21:54 来源:blog 网友评论 0 条 成员变量
介绍了类布局之后,我们接着考虑对不同的继承方式,访问成员变量的开销究竟如何。
没有继承。没有任何继承关系时,访问成员变量和C语言的情况完全一样:从指向对象的指针,考虑一定的偏移量即可。
| C* pc; pc->c1; // *(pc + dCc1); |
译者注:pc是指向C的指针。
· 访问C的成员变量c1,只需要在pc上加上固定的偏移量dCc1(在C中,C指针地址与其c1成员变量之间的偏移量值),再获取该指针的内容即可。
单继承。由于派生类实例与其基类实例之间的偏移量是常数0,所以,可以直接利用基类指针和基类成员之间的偏移量关系,如此计算得以简化。
| D* pd; pd->c1; // *(pd + dDC + dCc1); // *(pd + dDc1); pd->d1; // *(pd + dDd1); |
译者注:D从C单继承,pd为指向D的指针。
·当访问基类成员c1时,计算步骤本来应该为“pd+dDC+dCc1”,即为先计算D对象和C对象之间的偏移,再在此基础上加上C对象指针与成员变量c1之间的偏移量。然而,由于dDC恒定为0,所以直接计算C对象地址与c1之间的偏移就可以了。
·当访问派生类成员d1时,直接计算偏移量。
多重继承。虽然派生类与某个基类之间的偏移量可能不为0,然而,该偏移量总是一个常数。只要是个常数,访问成员变量,计算成员变量偏移时的计算就可以被简化。可见即使对于多重继承来说,访问成员变量开销仍然不大。
| F* pf; pf->c1; // *(pf + dFC + dCc1); // *(pf + dFc1); pf->e1; // *(pf + dFE + dEe1); // *(pf + dFe1); pf->f1; // *(pf + dFf1); |
译者注:F继承自C和E,pf是指向F对象的指针。
·访问C类成员c1时,F对象与内嵌C对象的相对偏移为0,可以直接计算F和c1的偏移;
·访问E类成员e1时,F对象与内嵌E对象的相对偏移是一个常数,F和e1之间的偏移计算也可以被简化;
·访问F自己的成员f1时,直接计算偏移量。
虚继承。当类有虚基类时,访问非虚基类的成员仍然是计算固定偏移量的问题。然而,访问虚基类的成员变量,开销就增大了,因为必须经过如下步骤才能获得成员变量的地址:获取“虚基类表指针”;获取虚基类表中某一表项的内容;把内容中指出的偏移量加到“虚基类表指针”的地址上。然而,事情并非永远如此。正如下面访问I对象的c1成员那样,如果不是通过指针访问,而是直接通过对象实例,则派生类的布局可以在编译期间静态获得,偏移量也可以在编译时计算,因此也就不必要根据虚基类表的表项来间接计算了。
| I* pi; pi->c1; // *(pi + dIGvbptr + (*(pi+dIGvbptr))[1] + dCc1); pi->g1; // *(pi + dIG + dGg1); // *(pi + dIg1); pi->h1; // *(pi + dIH + dHh1); // *(pi + dIh1); pi->i1; // *(pi + dIi1); I i; i.c1; // *(&i + IdIC + dCc1); // *(&i + IdIc1); |
译者注:I继承自G和H,G和H的虚基类是C,pi是指向I对象的指针。
·访问虚基类C的成员c1时,dIGvbptr是“在I中,I对象指针与G的“虚基类表指针”之间的偏移”,*(pi + dIGvbptr)是虚基类表的开始地址,*(pi + dIGvbptr)[1]是虚基类表的第二项的内容(在I对象中,G对象的“虚基类表指针”与虚基类之间的偏移),dCc1是C对象指针与成员变量c1之间的偏移;
·访问非虚基类G的成员g1时,直接计算偏移量;
·访问非虚基类H的成员h1时,直接计算偏移量;
·访问自身成员i1时,直接使用偏移量;
·当声明了一个对象实例,用点“.”操作符访问虚基类成员c1时,由于编译时就完全知道对象的布局情况,所以可以直接计算偏移量。
当访问类继承层次中,多层虚基类的成员变量时,情况又如何呢?比如,访问虚基类的虚基类的成员变量时?一些实现方式为:保存一个指向直接虚基类的指针,然后就可以从直接虚基类找到它的虚基类,逐级上推。VC++优化了这个过程。VC++在虚基类表中增加了一些额外的项,这些项保存了从派生类到其各层虚基类的偏移量。
强制转化
如果没有虚基类的问题,将一个指针强制转化为另一个类型的指针代价并不高昂。如果在要求转化的两个指针之间有“基类-派生类”关系,编译器只需要简单地在两者之间加上或者减去一个偏移量即可(并且该量还往往为0)。
| F* pf; (C*)pf; // (C*)(pf ? pf + dFC : 0); // (C*)pf; (E*)pf; // (E*)(pf ? pf + dFE : 0); |
C和E是F的基类,将F的指针pf转化为C*或E*,只需要将pf加上一个相应的偏移量。转化为C类型指针C*时,不需要计算,因为F和C之间的偏移量为0。转化为E类型指针E*时,必须在指针上加一个非0的偏移常量dFE。C++规范要求NULL指针在强制转化后依然为NULL,因此在做强制转化需要的运算之前,VC++会检查指针是否为NULL。当然,这个检查只有当指针被显示或者隐式转化为相关类型指针时才进行;当在派生类对象中调用基类的方法,从而派生类指针被在后台转化为一个基类的Const “this” 指针时,这个检查就不需要进行了,因为在此时,该指针一定不为NULL。
正如你猜想的,当继承关系中存在虚基类时,强制转化的开销会比较大。具体说来,和访问虚基类成员变量的开销相当。
| I* pi; (G*)pi; // (G*)pi; (H*)pi; // (H*)(pi ? pi + dIH : 0); (C*)pi; // (C*)(pi ? (pi+dIGvbptr + (*(pi+dIGvbptr))[1]) : 0); |
译者注:pi是指向I对象的指针,G,H是I的基类,C是G,H的虚基类。
·强制转化pi为G*时,由于G*和I*的地址相同,不需要计算;
·强制转化pi为H*时,只需要考虑一个常量偏移;
·强制转化pi为C*时,所作的计算和访问虚基类成员变量的开销相同,首先得到G的虚基类表指针,再从虚基类表的第二项中取出G到虚基类C的偏移量,最后根据pi、虚基类表偏移和虚基类C与虚基类表指针之间的偏移计算出C*。
一般说来,当从派生类中访问虚基类成员时,应该先强制转化派生类指针为虚基类指针,然后一直使用虚基类指针来访问虚基类成员变量。这样做,可以避免每次都要计算虚基类地址的开销。见下例。
| /* before: */ ... pi->c1 ... pi->c1 ... /* faster: */ C* pc = pi; ... pc->c1 ... pc->c1 ... |
译者注:前者一直使用派生类指针pi,故每次访问c1都有计算虚基类地址的较大开销;后者先将pi转化为虚基类指针pc,故后续调用可以省去计算虚基类地址的开销。
- 推荐阅讯
- 使用VC6.0实现窗口的任意分割
- Visual C# 2005快速入门之运用作用域
- VC++内部COM插件的编程实现
- 漫谈Visual C#的组件设计方法
- VC利用boost库解析正则表达式
- VC++中利用/GS开关防止缓冲区溢出
- 利用Visual C#实现ICMP网络协议
- Visual C#2005快速入门之声明bool变量
- 解读VC++编程中的文件操作API和CFile类
- VC#2005快速入门之使用布尔操作符
- 阅读排行
- 1.VC++编程实现广告窗口自动关闭
- 2.深入浅出VC++串口编程之基于控件
- 3.解读VC++编程中的文件操作API和CFile类
- 4.利用Visual C#实现ICMP网络协议
- 5.深入浅出VC++串口编程之第三方类
- 6.掀起你的盖头来——谈VC++对象模型
- 7.Visual C#中用WMI控制远程计算机
- 8.深入浅出VC++串口编程之基于Win32 API
- 9.Visual C++2005中开发自定义绘图控件
- 10.深入浅出VC++串口编程之基本概念
- 专题教程
- Windows Server-Windows Server文档-Windows Server新闻-Windows Ser PostgreSQL-PostgreSQL文档-PostgreSQL新闻-PostgreSQL专家
- WebLogic-WebLogic文档-WebLogic新闻-WebLogic专家 FreeBSD-FreeBSD文档-FreeBSD新闻-FreeBSD专家
- Linux-内核 GUI KDE Gnome DNS FTP 安全 安装-Linux专区 Windows-AD IIS ServerCore 虚拟化 安全 HPC-Windows专区
- 大话G游 专题:手机病毒揭密
- ARP攻击防范与解决方案 路由故障处理手册
