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

127 篇文章 7 订阅
39 篇文章 3 订阅

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

Declare destructors virtual in polymorphic base classes
该条款内容较多,分成两章来进行学习记录。

Virtual析构函数

首先,也是从一个例子入手。
对于时间的记录,可以有许多种方法。因此,设计一个TimeKeeper base class和一些derived classes以作为不同的计时方法是一种比较可取的方法:

class TimeKeeper {      //base class
public:
    TimeKeeper();
    ~TimeKeeper();      //Non-vitrual的析构函数
    ...
};

class AtomicClock : public TimeKeeper { ... }   //原子钟
class WaterClock: public TimeKeeper { ... }     //水钟
class WristWatch: public TimeKeeper { ... }     //腕表

在使用的过程中,用户可能只想在程序中使用时间, 而并不想操心时间的计算细节。
因此,这个时候,我们可以设计factory(工厂)函数,返回指针指向一个计时对象。即:

  • Factory函数会“返回一个base class指针, 指向新生成的derived class对象”。
TimeKeeper* getTimeKeeper();

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

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

...                                 //使用这个对象        
delete ptk;                         //释放这个对象,避免资源泄露

但是,在上述的代码中,纵使使用者把每一件事都做对了,仍然没有办法知道程序如何行动!

原因在于:

  • getTimeKeeper返回的指针指向一个derived class对象(例如AtomicClock),而这个对象却经由一个base class指针(例如TimeKeeper*指针)删除。但是,目前的base class(TimeKeeper)有一个non-virtual析构函数

之所以会引来错误,是因为C++明确指出,当derived class对象经由一个base class指针被删除,而该base class又带有一个non-virtual析构函数,这样操作的结果并没有定义

  • 实际执行时通常发生的是对象的derived成分没有被销毁

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

解决办法:

  • 给base class一个virtual析构函数。
    此后,删除derived class对象,就会销毁整个对象,包括所有的derived class的成分:
class TimeKeeper {      //base class
public:
    TimeKeeper();
    virtual ~TimeKeeper();      //vitrual的析构函数
    ...
};

TimeKeeper* ptk = getTimeKeeper();  

...                                         
delete ptk;     

像TimeKeeper这样的base classes除了析构函数之外通常还有其他的virtual函数,因为:

  • virtual函数的目的是允许derived class的实现得以客制化。

例如TimeKeeper就可能拥有一个virtual getCurrentTime,它在不同的derived classes中有不同的实现代代码。任何class只要带有virtual函数,就几乎可以确定也有一个virtual析构函数。

基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。
1.每个析构函数(不加 virtual) 只负责清除自己的成员。
2.可能有基类指针,指向的确是派生类成员的情况。(这是很正常的)
那么当析构一个指向派生类成员的基类指针时,程序就不知道怎么办了。
所以要保证运行适当的析构函数,基类中的析构函数必须为虚析构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值