Skip to content

Latest commit

 

History

History
88 lines (67 loc) · 3.51 KB

File metadata and controls

88 lines (67 loc) · 3.51 KB

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

有多种做法可以记录时间,因此,设计一个TimeKeeper基类和一些派生类作为不同的计时方法,相当合情合理:

class TimeKeeper {
public:
  TimeKeeper();
  ~TimeKeeper();
  ...
};
class AtomicClock : public TimeKeeper {...};
class WaterClock : public TimeKeeper {...};
class WristWatch : public TimeKeeper {...};

许多客户只想在程序中使用时间,不想操心如何计算时间等细节,这时我们可以设factory函数,返回指针的一个计时对象。factory函数会返回一个基类指针,指向新生成的派生类对象:

TimeKeeper* getTimeKeeper(); 

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

TimeKeeper* ptk = getTimeKeeper();
...
delete ptk;

这引来一个灾难,因为C++指出,当派生类对象经由一个基类指针被删除,而该基类带着一个non-trivial析构函数,其结果没有定义——实际执行时通常发生的是对象的派生部分没有被销毁,于是造成一个局部销毁对象。

消除这个问题的做法很简单:给基类一个虚析构函数。此后删除派生类对象就会如你想要的那般。它会销毁整个对象,包括所有派生类成分:

class TimeKeeper {
public:
  TimeKeeper();
  virtual ~TimeKeeper();
  ...
};
TimeKeeper* ptk = getTimeKeeper(); 
...
delete ptk;

无端地将所有类的析构函数声明为virtual,就像从未声明它们为virtual一样,都是错误的。只有当类内至少有一个virtual函数,才为它声明virtual析构函数。

即使类不完全带virtual函数,被non-trivial析构函数问题给咬伤还是有可能的。举个例子,标准string不含任何virtual函数,但有时程序员会错误地把它当作基类:

class SpecialString : public std::string {...};

乍看似乎无害,但如果你在程序某处无意间将一个SpecialString指针转换为要给string指针,然后将string指针delete掉,会导致未定义行为:

SpecialString* pss = new SpecialString("Impending Doom");
std::string* ps;
...
ps = pss;   // SpecialString* => std::string*
...
delete ps;  // UB

有时令class带一个纯虚析构函数,可能颇为便利。纯虚函数会导致抽象类——也就是不能被实例化地类。可以为你希望它成为抽象的那个类声明一个纯虚析构函数,可以为一个没有其他纯虚函数的类成为抽象类。下面是个例子:

class AWOV {  // abbr. abstract w/o virtual
public:
  virtual ~AWOV() = 0;
};

你必须为这个纯虚析构提供一份定义:

AWOV::~AWOV() {}

析构函数的运作方式是,最深层派生的那个类的析构函数最先被调用,然后是其每一个基类的析构函数被调用。编译器会在AWOV的派生类析构函数中创建一个对 ~AWOV() 的调用,所以你必须为这个函数提供定义。

“给一个基类一个虚析构函数”这个规则只适用于多态基类。并非所有的基类的设计目的都是为了多态用途,不需要虚析构函数。

请记住

  • 多态的基类应该声明一个virtual析构函数。如果类带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • 类的设计目的如果不是作为基类使用,或不是为了具备多态性,就不该声明virtual析构函数。