问题
base class的指针p,指向一个derived class,如果:
- base class带着一个non-virtual析构函数;
- derived class对象经由这个base class指针被删除;
这种情况一旦发生,结局就是:
其结果未有定义----实际执行时通常发生的是对象的derived成分没有被销毁
解决办法
解铃还需系铃人,作者说冤有头债有主,即然罪魁是base class的non-virtual析构函数,要解决的话最直接的方法就是给他一个virtual析构函数就行了。
再理解virtual函数
说到这,作者的话匣子就打开了,会出现以上的问题,究其原因是码农对virtual函数的思想理解不透彻。
公理1:virtual函数的目的是允许derived class的实现得以客制化。
客制化的最大结果就是多态,一个base class的指针可以实际指向各种客制化的derived class,所以如果一个class根本不打算用来做base class进而利用多态的化,最好就是压根不要给它实现任virtual函数。给了virtual函数就必须考虑derived class的客制化和多态。
推论1:任何class只要带有virtual函数几乎确定应该也有一个virtual析构函数。
这可以理解为公理1的一个推论,即然virtual函数的目的是允许derived class的客制化,那本体必然会是一个base class,作为一个必然(基本上可以认为是必然)会生出derived class,因此为了避免出现开头所述的问题,其析构函数也必然应该是virtual的。
推论2:如果class不含virtual函数,通常表示它并不意图被用作一个base class;
当class并不企图被当作base class,令其析构函数为virtual往往是个馊主意。
这是因为一个class如果含有哪怕一个virtual函数,为了存放vptr指针,其size都会增大。
这里需要区分【条款6】中提到的Uncopyable class,Uncopyable class虽然必然是一个base class,但是其目的并不是为了客制化或者多态,因此其可以没有virtual函数,包括析构函数也不必是virtual的。
而对于目的是客制化+多态的base class,有一个更专业的称呼叫做polymorphic base class。
需要注意的坑
现在大家都应该明白了,不要去继承不带virtual析构函数的base class,如果自己写的一个类会作为base class的话,则必须给其提供virtual析构函数。但是有种特殊情况需要注意:避免去继承任何STL容器和标准string,这些东西的析构函数都是non-virtual的。
STL的string 和所有STL容器的析构函数都是non-virtual的,因此不要继承他们。
更优雅的做法
定义abstract class,该class的声明里只包含一个pure virtual析构函数,这隐含两层意思:
- pure virtual函数导致本体必然是个abstract class;
- abstract class不能被实体化,必然只能用来做为base class;
这样一来,所有的子孙derived class的析构函数都必然是virtual函数了。
需要注意,这个pure virtual 虚构函数必须要提供一个定义,因为derived class被析构时会调用这个base class的析构函数。
class AWOV { //AWOA = "Abstract w/o Vitruals
public:
virtual ~AWOV()=0; //声明pure virtual析构函数
};
AWOV::~AWOV(){} //pure virtual析构函数的定义