条款07:为多态基类声明virtual析构函数

目录

1.前言

2.Virtual析构的正确用法

3.扩展


1.前言

这里设计一个TimeKeeper base class和一些derived classes作为不同的计时方法,例子

如下:

class TimeKeeper{
    public:
        TimeKeeper();
        ~TimeKeeper();
        ....
};
class AtomicClock:public TimeKeeper{....};//原子钟
class WaterClock:public TimerKeeper{.....};//水钟
class WristWatch:public TimeKeeper{.....};//腕表

在程序编写时,程序员只想在程序中使用时间,而不想操心时间如何被计算等这些细节,这时候我们可以设计factory(工厂)函数(参考设计模式知识),返回指针指向一个计时对象。Factory函数会“返回一个base class指针,指向新生成的derived class对象”。实例如下:

TimeKeeper* getTimeKeeper();//返回一个指针,指向一个TimeKeeper派生类的动态分配对象

为遵守factory函数的规矩,被getTimeKeeper()返回的对象必须位于heap。为了避免内存泄露和其他资源,将factory函数返回的每一个对象进行delete非常重要:

TimeKeeper* ptk=getTimeKeeper();//从TimeKeeper继承体系获得一个动态分配对象

...//运用它
delete ptk;//释放它,避免内存泄露。

上述代码的运行会造成严重的内存泄露,问题出在getTimeKeeper返回的指针指向一个derived class对象(例如AtomicClock),而那个对象却被一个base class指针(例如一个TimeKeeper*指针)被删除,而目前的base class(TimeKeeper)有个non-virtual析构函数。因为这样实际执行时通常导致的后果是derived成分没有被销毁。

比如getTimeKeeper返回的指针指向一个AtomicClock对象,其内的AtomicClock成分(也就是声明于AtomicClock class内的成员变量)很可能没被销毁,而AtomicClock的析构函数也未被执行。然而其base class成分(也就是TimeKeeper这一部分)通常会被销毁,于是造成一个诡异的“局部销毁”对象。

2.Virtual析构的正确用法

解决上面这个问题很简单:即给base class一个virtual析构函数。这样删除derived class对象就会按照正确流程执行,包括derived  class成分。

class TimeKeeper{

    public:
        TimeKeeper();
        virtual ~TimeKeeper();
        ....
};

TimeKeeper* ptk=getTimeKeeper();
....
delete ptk;//现在,不会造成资源泄露

任何一个class只要带有virtual函数都几乎确定应该有一个virtual析构函数。

3.扩展

如果class不含virtual函数,通常表明它并不是被用做一个base class。当class不企图当作base class,令其析构函数为virtual往往是个馊注意假设有一个表示二维空间点的坐标class:

class point{//一个二维空间点(2D point)

    public:
        point(int xCoord,int yCoord);
        ~point();

    private:
        int x,y;
};

如果int占用32bits,那么point对象可塞入一个64-bit缓冲器中。也可以将point对象当作一个64-bit量;

要实现virtual函数,对象必须携带某些信息,主要用来决定在运行期间决定哪一个virtual函数该被调用。该份携带的信息通常是由一个所谓的vptr(virtual talble pointer)指针指出。vptr指向一个由函数指针构成的数组,成为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指向的那个vtbl-编译器在其中寻找适当的函数指针。

vitual函数的实现细节不重要。重要的是假设Point class内含virtual函数,其对象的内存会增加:

在32bit计算机体系结构中将占用64Bits(为了存放两个ints)到96bits(两个ints加上vptr);因此,为point添加一个vptr会增加其对象大小的50%-100%。point对象不再能够塞入一个64-bit缓冲器。

因此,无端地将所有classes的析构函数声明为virtual,是不合理的。

  • 13
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值