您的位置: 首页 N搜咨询 文章阅读 C++中确定基类有虚析构函数
打印本页 放大字体 关闭本页
C++中确定基类有虚析构函数

作者:N搜网友 编辑:N搜网 录入:N搜网 来源:N搜网络
录入时间:2006-8-17 更新时间:2006-8-17 点击次数:582
主标题:C++中确定基类有虚析构函数
副标题:C++中确定基类有虚析构函数
短标题:C++中确定基类有虚析构函数
 
    有时,一个类想跟踪它有多少个对象存在。一个简单的方法是创建一个静态类成员来统计对象的个数。这个成员被初始化为0,在构造函数里加1,析构函数里减1。(条款m26里说明了如何把这种方法封装起来以便很容易地添加到任何类中,“my article on counting objects”提供了对这个技术的另外一些改进)
    设想在一个军事应用程序里,有一个表示敌人目标的类:
    class enemytarget {
    public:
    enemytarget() { ++numtargets; }
    enemytarget(const enemytarget&) { ++numtargets; }
    ~enemytarget() { --numtargets; }
    static size_t numberoftargets()
    { return numtargets; }
    virtual bool destroy();       // 摧毁enemytarget对象后
    // 返回成功
    private:
    static size_t numtargets;     // 对象计数器
    };
    // 类的静态成员要在类外定义;
    // 缺省初始化为0
    size_t enemytarget::numtargets;
    这个类不会为你赢得一份政府防御合同,它离国防部的要求相差太远了,但它足以满足我们这儿说明问题的需要。
    敌人的坦克是一种特殊的敌人目标,所以会很自然地想到将它抽象为一个以公有继承方式从enemytarget派生出来的类(参见条款35及m33)。因为不但要关心敌人目标的总数,也要关心敌人坦克的总数,所以和基类一样,在派生类里也采用了上面提到的同样的技巧:
    class enemytank: public enemytarget {
    public:
    enemytank() { ++numtanks; }
    enemytank(const enemytank& rhs)
    : enemytarget(rhs)
    { ++numtanks; }
    ~enemytank() { --numtanks; }
    static size_t numberoftanks()
    { return numtanks; }
    virtual bool destroy();
    private:
    static size_t numtanks;         // 坦克对象计数器
    };
   (写完以上两个类的代码后,你就更能够理解条款m26对这个问题的通用解决方案了。)
    最后,假设程序的其他某处用new动态创建了一个enemytank对象,然后用delete删除掉:
    enemytarget *targetptr = new enemytank;
    ...
    delete targetptr;
    到此为止所做的一切好象都很正常:两个类在析构函数里都对构造函数所做的操作进行了清除;应用程序也显然没有错误,用new生成的对象在最后也用delete删除了。然而这里却有很大的问题。程序的行为是不可预测的——无法知道将会发生什么。
    c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。这意味着编译器生成的代码将会做任何它喜欢的事:重新格式化你的硬盘,给你的老板发电子邮件,把你的程序源代码传真给你的对手,无论什么事都可能发生。(实际运行时经常发生的是,派生类的析构函数永远不会被调用。在本例中,这意味着当targetptr 删除时,enemytank的数量值不会改变,那么,敌人坦克的数量就是错的,这对需要高度依赖精确信息的部队来说,会造成什么后果?)
    为了避免这个问题,只需要使enemytarget的析构函数为virtual。声明析构函数为虚就会带来你所希望的运行良好的行为:对象内存释放时,enemytank和enemytarget的析构函数都会被调用。
    和绝大部分基类一样,现在enemytarget类包含一个虚函数。虚函数的目的是让派生类去定制自己的行为(见条款36),所以几乎所有的基类都包含虚函数。
    如果某个类不包含虚函数,那一般是表示它将不作为一个基类来使用。当一个类不准备作为基类使用时,使析构函数为虚一般是个坏主意。请看下面的例子,这个例子基于arm(“the annotated c++ reference manual”)一书的一个专题讨论。
    // 一个表示2d点的类
    class point {
    public:
    point(short int xcoord, short int ycoord);
    ~point();
    private:
    short int x, y;
    };
    如果一个short int占16位,一个point对象将刚好适合放进一个32位的寄存器中。另外,一个point对象可以作为一个32位的数据传给用c或fortran等其他语言写的函数中。但如果point的析构函数为虚,情况就会改变。
    实现虚函数需要对象附带一些额外信息,以使对象在运行时可以确定该调用哪个虚函数。对大多数编译器来说,这个额外信息的具体形式是一个称为vptr(虚函数表指针)的指针。vptr指向的是一个称为vtbl(虚函数表)的函数指针数组。每个有虚函数的类都附带有一个vtbl。当对一个对象的某个虚函数进行请求调用时,实际被调用的函数是根据指向vtbl的vptr在vtbl里找到相应的函数指针来确定的。
    虚函数实现的细节不重要(当然,如果你感兴趣,可以阅读条款m24),重要的是,如果point类包含一个虚函数,它的对象的体积将不知不觉地翻番,从2个16位的short变成了2个16位的short加上一个32位的vptr!point对象再也不能放到一个32位寄存器中去了。而且,c++中的point对象看起来再也不具有和其他语言如c中声明的那样相同的结构了,因为这些语言里没有vptr。所以,用其他语言写的函数来传递point也不再可能了,除非专门去为它们设计vptr,而这本身是实现的细节,会导致代码无法移植。
    所以基本的一条是,无故的声明虚析构函数和永远不去声明一样是错误的。实际上,很多人这样总结:当且仅当类里包含至少一个虚函数的时候才去声明虚析构函数。
    这是一个很好的准则,大多数情况都适用。但不幸的是,当类里没有虚函数的时候,也会带来非虚析构函数问题。 例如,条款13里有个实现用户自定义数组下标上下限的类模板。假设你(不顾条款m33的建议)决定写一个派生类模板来表示某种可以命名的数组(即每个数组有一个名字)。
    template<class t> // 基类模板
    class array { // (来自条款13)
    public:
    array(int lowbound, int highbound);
    ~array();
    private:
    vector<t> data;
    size_t size;
    int lbound, hbound;
    };
    template<class t>
    class namedarray: public array<t> {
    public:
    namedarray(int lowbound, int highbound, const string& name);
    ...
    private:
    string arrayname;
    };
    如果在应用程序的某个地方你将指向namedarray类型的指针转换成了array类型的指针,然后用delete来删除array指针,那你就会立即掉进“不确定行为”的陷阱中。
    namedarray<int> *pna =
    new namedarray<int>(10, 20, "impending doom");
    array<int> *pa;
    ...
    pa = pna; // namedarray<int>* -> array<int>*
    ...
    delete pa; // 不确定! 实际中,pa->arrayname
    // 会造成泄漏,因为*pa的namedarray
    // 永远不会被删除
    现实中,这种情形出现得比你想象的要频繁。让一个现有的类做些什么事,然后从它派生一个类做和它相同的事,再加上一些特殊的功能,这在现实中不是不常见。namedarray没有重定义array的任何行为——它继承了array的所有功能而没有进行任何修改——它只是增加了一些额外的功能。但非虚析构函数的问题依然存在(还有其他问题,参见m33)
    最后,值得指出的是,在某些类里声明纯虚析构函数很方便。纯虚函数将产生抽象类——不能实例化的类(即不能创建此类型的对象)。有些时候,你想使一个类成为抽象类,但刚好又没有任何纯虚函数。怎么办?因为抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。
    这里是一个例子:
    class awov {         // awov = "abstract w/o
    // virtuals"public:
    virtual ~awov() = 0; // 声明一个纯虚析构函数
    };
    这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:
    awov::~awov() {} // 纯虚析构函数的定义
    这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。
    可以在函数里做任何事,但正如上面的例子一样,什么事都不做也不是不常见。如果是这种情况,那很自然地会想到将析构函数声明为内联函数,从而避免对一个空函数的调用所产生的开销。这是一个很好的方法,但有一件事要清楚。
    因为析构函数为虚,它的地址必须进入到类的vtbl(见条款m24)。但内联函数不是作为独立的函数存在的(这就是“内联”的意思),所以必须用特殊的方法得到它们的地址。条款33对此做了全面的介绍,其基本点是:如果声明虚析构函数为inline,将会避免调用它们时产生的开销,但编译器还是必然会在什么地方产生一个此函数的拷贝。
    构造函数是用初始化类能者对象的,是不允许用虚函数的。
  [N搜网-中国网上商店商品服务搜索门户]:[本文章由N搜网于2006-8-17录入系统,网址:www.nsall.com

打印本页 放大字体 关闭本页
 
 
相关主题文章
C++中的const限定修饰符 C++中的虚函数(virtual function)
C++中确定基类有虚析构函数 C++中用vectors改进内存的再分配
C++中用函数模板实现和优化抽象操作 CBuilder中帮助文件的连接及显示讨论
CRC循环校验的具体算法 C标准中一些预定义的宏
C程式中关于整数储存的说明 C数值计算程序移植到VC开发环境
C语言初学者入门讲座 第八讲 转移语句 C语言初学者入门讲座 第二讲 数据类型(1)
C语言初学者入门讲座 第二讲 数据类型(2) C语言初学者入门讲座 第二讲 数据类型(3)
C语言初学者入门讲座 第九讲 数组(1) C语言初学者入门讲座 第九讲 数组(2)
C语言初学者入门讲座 第六讲 分支结构(1) C语言初学者入门讲座 第六讲 分支结构(2)
C语言初学者入门讲座 第七讲 循环结构 C语言初学者入门讲座 第三讲 基础语句
C语言初学者入门讲座 第十二讲 结构(1) C语言初学者入门讲座 第十二讲 结构(2)
C语言初学者入门讲座 第十二讲 结构(3) C语言初学者入门讲座 第十讲 函数(1)
C语言初学者入门讲座 第十讲 函数(2) C语言初学者入门讲座 第十讲 函数(3)
C语言初学者入门讲座 第十讲 函数(4) C语言初学者入门讲座 第十六讲 文件(1)
C语言初学者入门讲座 第十六讲 文件(2) C语言初学者入门讲座 第十三讲 联合
C语言初学者入门讲座 第十四讲 枚举与位运算(1) SNMP用VC++6.0实现的方法
XML在系统日志设计中的运用 利用VC++编写Windows95的CPL组件
利用VC++开发所见即所得的打印程序 利用VB自制OCX控件
利用Visual Basic 实现无线通讯 利用VC实现AVI文件的图像截取
利用VB实现宽行打印的一个技巧 调试Release版本应用程序
利用VC++编程实现程序自动启动 利用VB制作MP3播放列表
利用VC++开发ASP图像处理组件 利用VC++获取异构型数据库库结构信息
如何在ASP.Net 中把图片存入数据库 用TAPI为掌上电脑开发通讯应用程序
让窗口一直在上面 通过DELPHI小程序在WINDOWS下更好地使用DOS批处理…
用ASP实现的2000年倒记时程序 在vb组件内调用excel2000实现GIF饼图
用VB编写接近实际的抽奖程序 微软风格的Explore
在 C# 中使用画笔 用VB6分离出文本框的单词
如何拦截键盘输入 在VB5.0下有效控制鼠标的输入焦点
用VB5创建B/S程序 在ASP中用“正则表达式对象”来校验数据的合法性…
用VB实现局域网屏幕监视 用ASP实现网上考试系统
在ASP中利用“正则表达式” 对象实现UBB风格… [组图] 用Excel的宏编制个人证券账户管理器
用Powerbuilder进行分布式应用开发三级体系结构 在Asp.Net中从sqlserver检索(retrieve)图片
论游戏中的搜索问题(初级篇) 用VB调试串口通讯
使用API创建窗体(类似VC的创建过程) 用Delphi做一个OpenGL控件
如何在程序中使用自己的库单元 消息队列在VB.NET数据库开发中的应用
在VFP5.0中实现中英文自动切换 在BCB下使用GExperts的Debug功能
用FoxWeb在网上快速发布你的FOXPRO数据库 用VB6.0设计一个打字练习软件
在VFP中实现跟变式组合框及椭圆图形菜单 用Delphi合并Word表格中单元格
用BCB实现超星格式转换为BMP格式 拼图游戏的算法
在拷贝、删除文件时显示飞行的文件夹动画 在PB中如何使用Microsoft Outlook发送邮件
在PowerBuilder中调用ChooseColor函数 一个简单的MP3播放器
如何用pb实现MSACCESS数据库的图片字段存取 在C++Builder中使用Compress Html Help
压缩HTMl文件 在powerbuilder中向Excel传递数据
人民币大小写转换算法 在PB中应用灵活多样的排序
如何在程序启动默认浏览器与电子邮件系统 用C#编写获取远程IP,MAC的方法
用API实现在MSN的信息提示 真正意义上的前台窗口切换
如何在ASP程序中打印Access报表 如何在Windows应用程序中实现电子注册功能
上楼梯算法的java实现 实时曲线的绘制和保存
用Vb.net实现自定义界面 刘徽《九章算术》中的勾股数
ASCII码表 水仙花数
 
 
 
本站关键字:网上商店商品服务大全 网上购物导航 在线购物搜索引擎 网店比较购物 网络商城 特色网上超市商店 网上网络开店购物