掀起你的盖头来——谈VC++对象模型
发布时间:2006-02-28 12:21:54 来源:blog 网友评论 0 条 数组
堆上分配空间的数组使虚析构函数进一步复杂化。问题变复杂的原因有两个:
1、堆上分配空间的数组,由于数组可大可小,所以,数组大小值应该和数组一起保存。因此,堆上分配空间的数组会分配额外的空间来存储数组元素的个数;
2、当数组被删除时,数组中每个元素都要被正确地释放,即使当数组大小不确定时也必须成功完成该操作。然而,派生类可能比基类占用更多的内存空间,从而使正确释放比较困难。
| struct WW : W { int w1; }; pv = new W[m]; delete [] pv; // delete m W's (sizeof(W) == sizeof(V)) pv = new WW[n]; delete [] pv; // delete n WW's (sizeof(WW) > sizeof(V)) |
译者注:WW从W继承,增加了一个成员变量,因此,WW占用的内存空间比W大。然而,不管指针pv指向W的数组还是WW的数组,delete[]都必须正确地释放WW或W对象占用的内存空间。
虽然从严格意义上来说,数组delete的多态行为C++标准并未定义,然而,微软有一些客户要求实现该行为。因此,在MSC++中,该行为是用另一个编译器生成的虚析构帮助函数来完成。该函数被称为“向量delete析构函数”(因其针对特定的类定制,比如WW,所以,它能够遍历数组的每个元素,调用对每个元素适用的析构函数)。
异常处理
简单说来,异常处理是C++标准委员会工作文件提供的一种机制,通过该机制,一个函数可以通知其调用者“异常”情况的发生,调用者则能据此选择合适的代码来处理异常。该机制在传统的“函数调用返回,检查错误状态代码”方法之外,给程序提供了另一种处理错误的手段。
因为C++是面向对象的语言,很自然地,C++中用对象来表达异常状态。并且,使用何种异常处理也是基于“抛出的”异常对象的静态或动态类型来决定的。不光如此,既然C++总是保证超出范围的对象能够被正确地销毁,异常实现也必须保证当控制从异常抛出点转换到异常“捕获”点时(栈展开),超出范围的对象能够被自动、正确地销毁。
考虑如下例子:
| struct X { X(); }; // exception object class struct Z { Z(); ~Z(); }; // class with a destructor extern void recover(const X&); void f(int), g(int); int main() { try { f(0); } catch (const X& rx) { recover(rx); } return 0; } void f(int i) { Z z1; g(i); Z z2; g(i-1); } void g(int j) { if (j < 0) throw X(); } |
译者注:X是异常类,Z是带析构函数的工作类,recover是错误处理函数,f和g一起产生异常条件,g实际抛出异常。
这段程序会抛出异常。在main中,加入了处理异常的try & catch框架,当调用f(0)时,f构造z1,调用g(0)后,再构造z2,再调用g(-1),此时g发现参数为负,抛出X异常对象。我们希望在某个调用层次上,该异常能够得到处理。既然g和f都没有建立处理异常的框架,我们就只能希望main函数建立的异常处理框架能够处理X异常对象。实际上,确实如此。当控制被转移到main中异常捕获点时,从g中的异常抛出点到main中的异常捕获点之间,该范围内的对象都必须被销毁。在本例中,z2和z1应该被销毁。
谈到异常处理的具体实现方式,一般情况下,在抛出点和捕获点都使用“表”来表述能够捕获异常对象的类型;并且,实现要保证能够在特定的捕获点真正捕获特定的异常对象;一般地,还要运用抛出的对象来初始化捕获语句的“实参”。通过合理地选择编码方案,可以保证这些表格不会占用过多的内存空间。
异常处理的开销到底如何?让我们再考虑一下函数f。看起来f没有做异常处理。f确实没有包含try,catch,或者是throw关键字,因此,我们会猜异常处理应该对f没有什么影响。错!编译器必须保证一旦z1被构造,而后续调用的任何函数向f抛回了异常,异常又出了f的范围时,z1对象能被正确地销毁。同样,一旦z2被构造,编译器也必须保证后续抛出异常时,能够正确地销毁z2和z1。
要实现这些“展开”语意,编译器必须在后台提供一种机制,该机制在调用者函数中,针对调用的函数抛出的异常动态决定异常环境(处理点)。这可能包括在每个函数的准备工作和善后工作中增加额外的代码,在最糟糕的情况下,要针对每一套对象初始化的情况更新状态变量。例如,上述例子中,z1应被销毁的异常环境当然与z2和z1都应该被销毁的异常环境不同,因此,不管是在构造z1后,还是继而在构造z2后,VC++都要分别在状态变量中更新(存储)新的值。
所有这些表,函数调用的准备和善后工作,状态变量的更新,都会使异常处理功能造成可观的内存空间和运行速度开销。正如我们所见,即使在没有使用异常处理的函数中,该开销也会发生。
幸运的是,一些编译器可以提供编译选项,关闭异常处理机制。那些不需要异常处理机制的代码,就可以避免这些额外的开销了。
小结
好了,现在你可以写C++编译器了(开个玩笑)。
在本文中,我们讨论了许多重要的C++运行实现问题。我们发现,很多美妙的C++语言特性的开销很低,同时,其他一些美妙的特性(译者注:主要是和“虚”字相关的东西)将造成较大的开销。C++很多实现机制都是在后台默默地为你工作。一般说来,单独看一段代码时,很难衡量这段代码造成的运行时开销,必须把这段代码放到一个更大的环境中来考察,运行时开销问题才能得到比较明确的答案。
- 推荐阅讯
- 使用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攻击防范与解决方案 路由故障处理手册
