文章目录
- 虚析构函数
- 声明虚析构函数的情况
- 继承的情况
- 总结
虚析构函数
纪录时间的方法很多,合理的做法是创建一个基类,并为不同的计时方法创建派生类:
class TimeKeeper {
public:TimeKeeper();~TimeKeeper();...
};
class AtomicClock : public TimeKeeper { ... };
class WaterClock : public TimeKeeper { ... };
class WristWatch : public TimeKeeper { ... };TimeKeeper* getTimeKeeper(); // 返回一个指针,指向一个动态分配的派生类对象//…TimeKeeper* ptk = getTimeKeeper(); // 获取动态分配的对象... // 使用它delete ptk; // 释放它以避免资源泄漏
我们可以看到派生类的析构函数没有执行。
- 当通过指向具有非虚析构函数的基类的指针删除派生类对象时,结果未定义。
- 在运行时,对象的派生部分通常不会被销毁,基类部分会销毁,从而导致一个奇怪的“部分销毁”的对象。
消除这个问题很简单:为基类提供一个虚拟析构函数。然后delete派生类对象就会达到想要的效果。它将销毁整个对象,包括它的所有派生类部分:
class TimeKeeper {
public:TimeKeeper();virtual ~TimeKeeper();...
};
TimeKeeper* ptk = getTimeKeeper();
...
delete ptk; // 现在行为正确了
声明虚析构函数的情况
既然将析构函数声明为虚函数可以避免派生类析构函数不执行的问题,那么我们是不是把所有类的析构函数都声明为虚函数就行了呢?答案是不行。当一个类不打算成为基类时,将析构函数设为虚函数通常不是一个好主意。
class Point { // 一个2D的点
public:Point(int xCoord, int yCoord);~Point();
private:int x, y;
};
主要原因如下:
使用虚函数需要而外的,名为vptr(virtual table pointer)的指针,vptr指向一个函数指针数组,称为vtbl (virtual table)。这让这个类的大小凭空多了四个字节的大小。
继承的情况
标准的string类型不包含虚函数,但有时候我们会写个类继承他。
class SpecialString : public std::string { // 坏主意!string有非虚析构函数};SpecialString* pss = new SpecialString("Impending Doom");
std::string* ps;
...
ps = pss; // SpecialString* std::string*
...
delete ps; // 未定义的! 实际上派生类部分会被泄露,不会调用SpecialString析构函数
有时你想创造一个抽象类,但却没有任何纯虚函数。怎么办?
解决方案很简单:在想要抽象的类中声明一个纯虚析构函数。
class AWOV { // AWOV = "Abstract w/o Virtuals"
public:virtual ~AWOV() = 0; // 声明纯虚析构函数
};
AWOV::~AWOV() {} // 必须提供纯虚析构函数的定义
总结
- 多态基类应该声明虚析构函数。如果一个类有虚函数,它应该有一个虚析构函数。
- 非基类或非多态使用的类不应该声明虚析构函数。