0.概述
- 带有多态性质的基类应该声明一个虚析构函数。如果类中带有任何一个虚函数,就应该拥有一个虚析构函数。
- 类的设计目的如果不是为了作为基类,或不是为了具备多态性,就不该声明虚析构函数。
1.非虚析构函数带来的问题
设计一个TimeKeeper基类和一些派生类作为不同的计时方法:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };
设计工厂(factory)函数,返回一个基类指针,指向新生成的派生类对象。
//返回一个指针,指向一个TimeKeeper派生类的动态分配对象
TimeKeeper* getTimeKeeper();
问题:getTimeKeeper返回的指针指向派生类对象,但那个对象却经由一个基类指针被删除,而目前的基类含有非虚析构函数。这有可能会导致对象的派生成分没有被销毁,造成一个”局部销毁“对象。
解决方案:给基类一个虚析构函数。
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();
...
};
通常如果类中带有任何一个虚函数,就应该拥有一个虚析构函数。
2.虚析构函数带来的问题
如果类中不含有虚函数,表示他不被用作一个基类,这时候不能将其析构函数设为虚函数。举例:
class Point { // 一个2D点
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
从占用内存的角度考虑,如果int对象占用32bits,那么Points对象可以被放入一个64bits的缓存器中。
如果Point class 内含virtual函数,其对象的体积会增加:在32-bit计算机体系结构中将占用64 bits (为了存放两个ints)至96 bits(两个ints 加上 vptr)﹔在64-bit计算机体系结构中可能占用64~128 bits,因为指针在这样的计算机结构中占64 bits。
因此,为Point添加一个vptr会增加其对象大小达50%~1008!Point对象不再能够塞入一个64-bit缓存器,而C++的Point对象也不再和其他语言(如C)内的相同声明有着一样的结构(因为其他语言的对应物并没有vptr),因此也就不再可能把它传递至(或接受自)其他语言所写的函数,除非你明确补偿vptr—一那属于实现细节,也因此不再具有移植性。
3.禁止派生
有时程序员可能会错误地把不含任何虚函数的类当作基类,如标准string类;或任何不带虚析构函数的类,包括所有的STL容器如vector、list、set、tr1::unordered_map等。
4.纯虚析构函数,抽象类
纯虚函数会导致抽象类(不能被实体化,不能为其创建对象)。
可以为希望成为抽象类的那个类声明一个纯虚析构函数,如下:
class AWOV {
public:
virtual ~AWOV() = 0; // 声明纯虚析构函数
};
这个类有一个纯虚析构函数,因此是个抽象类;由于有虚析构函数,所以不必担心析构函数的问题(完美闭环)。但是必须为这个纯虚析构函数提供一个定义。
AWOV::~AWOV(){ }