Effective C++ 条款07_不止于此

为多态基类声明 virtual 析构函数

举个例子

  • 有许多种方法可以记录时间,因此,设计一个 TimeTracer base class 和一些 derived classes 作为不同的计时方法比较方便:
class TimeTracer {
    
    
public:
	TimeTracer(){
    
    ...};
	~TimeTracer(){
    
    ...};
};

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

许多客户只想在程序中使用时间,而不想操心时间如何计算等细节,这个时候我们可以设计一个 factory(工厂)函数,返回指针指向一个计时对象。factory 函数会 “ 返回一个 base class 指针,指向新生成的 derived class 对象 ” :

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

但是问题来了,getTimeTracer 返回的对象必须位于 heap(堆)。因此为了避免泄露内存和其他资源,将其返回的每个对象适当地 delete 掉很重要:

TimeTracer* ptr = getTimeTracer();

...    //运用它
delete ptr;  //释放它,避免资源泄露

但在本例中只是这样执行的话会带来严重的灾难,因为 C++ 明确指出,当 derived class 对象经由一个 base class 指针被删除,而该 base class 带着一个 non-virtual 析构函数,其结果未有定义——实际执行是通常发生的是对象的derived 成分没被销毁。


也就是说如果 getTimeTracer 返回指针指向一个 AtomicClock 对象,其内的 AtomicClock 成分(也就是声明于 AtomicClock class 捏的成员变量)很可能没被销毁,而 AtomicClock 的析构函数也未能执行起来。然而它的 base class成分(也就是 TimeTracer 这一部分)通常会被销毁,于是造成了一个诡异的 “ 局部销毁 ” 对象。这很有可能造成资源泄露、败坏之数据结构、在调试器上浪费许多时间的严重后果。


想要解决这个问题很简单,只要把给 base class 一个 virtual 析构函数就好了。此后删除 derived class 对象就会如你想要的那般销毁整个对象:

class TimeTracer {
    
    
public:
	TimeTracer(){
    
    ...};
	virtual ~TimeTracer(){
    
    ...};
};
TimeTracer* ptr = getTimeTracer();

...    //运用它
delete ptr;  //现在,行为正确!释放它,避免资源泄露

任何 class 只要带有 virtual 函数都几乎确定应该也有一个 virtual 析构函数
如果 class 不含 virtual 函数,通常表示它并不意图被用作一个 base class。当 class 不企图被当做 base class,令其析构函数为 virtual 往往是个馊主意。


我们还应遵循这样一条规则:为你希望它成为抽象的那个 class 声明一个 pure virtual 析构函数:

class Awoc {
    
    
public:
	virtual ~Awoc() = 0;
};  // 这个 class 有一个 pure virtual 函数,所以是抽象类,又因为它有一个 pure virtual 析构函数,所以你不需要担心析构函数的问题。

//但必须为它提供一份定义:
Awoc::~Awoc() {
    
     }

这里虚构函数的运作方式是,最深层派生的那个 class 的析构函数最先被调用,然后是其每一个 base class 的析构函数被调用。
说明:
编译器会在 Awoc 的 derived classes 的析构函数中创建一个对 ~Awoc 的调用动作,所以你必须为这个函数提供一份定义。如果不这样做,连接器会不通过。

  • 给 base classes 一个 virtual 析构函数,这个规则只适用于 polymorphic(带多态性质的)base class 身上。
  • 并非所有的 base classes 的设计都是为了多态用途。例如标准 string 和 STL 容器都不被设计作为 base class 使用,更别提多态了。

最后请记住:

  • polymorphic (带多态性质的) base classes 应该声明一个 virtual 析构函数。如果 class 带有任何 virtual 函数,它就应该拥有一个 virtual 析构函数。
  • Classes 的设计目的如果不是作为 base classes 使用,或不是为了具备多态性(polymorphically),就不该声明 virtual 析构函数。

猜你喜欢

转载自blog.csdn.net/weixin_48033173/article/details/108967981