天天看点

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

条款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.可能有基类指针,指向的确是派生类成员的情况。(这是很正常的)

那么当析构一个指向派生类成员的基类指针时,程序就不知道怎么办了。

所以要保证运行适当的析构函数,基类中的析构函数必须为虚析构。