问题描述
假设我们定义了一个base class,并随之延伸出一些derived class。用户只需要在程序中使用某个功能,并不关心其实现。因此,我们可以设计一个factory函数。(工厂模式详见More Effective C++ 25)
factory函数返回一个base clsss指针,指向新生成的derived class对象。 返回的对象必须位于heap,因此为了避免泄漏内存和其他资源,factory返回的每一个对象都应该被适当的delete。举例而言:1
2
3
4
5
6
7
8
9
10
11
12class TimeKeeper{//计时器基类
public:
TimeKeeper();
~TimeKeeper();//此处不应该使用non-virtual析构函数
...
}
class AtomicClock:public TimeKeeper{...};
class WaterClock:public TimeKeeper{...};
TimeKeeper* ptk=getTimeKeeper();//factory函数
...
delete ptk;释放以避免资源泄露
问题在于factory返回的指针指向一个derived class对象,而那个对象却经由一个base class指针被删除。如果base class的析构函数是non-virtual,则我们再次雪崩。
多态基类不使用virtual析构的后果
c++明确指出,当derived class对象由一个base class指针被删除,而base class带着一个non-virtual析构函数,其结果未定义 — 一般来说,实际执行时对象的derived成分没被销毁,derived class的析构函数也没执行。也就是说我们得到了一个诡异的局部销毁对象。
何时需要virtual析构函数?
一般来说,base class都会有那么一个virtual function。我们在这里指出,只要一个class有virtual function,我们都应该把它的析构函数定义为virtual。
如果一个class不被用作base class,那给他定义一个virtual 析构函数非常愚蠢。原因是这样的:
欲实现出virtual函数,对象必须携带某些信息,要来指出在运行期哪个virtual函数被调用。这份信息一般由一个virtual table pointer指出。vtp指向一个vbtl(virtual table)每一个带virtual函数的class都会有一个vtbl,当函数调用某个virtual函数,实际被调用的函数取决于对象的vptr所指向的vtbl—编译器在其中寻找函数指针。(关于虚函数的使用成本,详见More Effective C++ 24)
总之,虚函数的引入增加了class的大小,并且破坏了原有的内存结构。
另外,STL所有的类都不能作为base class,它们也没有virtual析构函数。
virtual析构函数在abstract class中的用法
有时我们希望定义一个abstract class,但是手头上并没有pure virtual function,那解决方法很简单:声明一个pure virtual 析构函数。
但事情还没完,我们不仅仅需要声明,还需要定义这个析构函数。可能你会诧异于pure function居然还需要实现,但实际上pure function和有没有实现并没有任何联系。(该论述详见More Effective C++ 33),具体声明和定义如下所示:1
2
3
4
5class c{
public:
virtual ~c() = 0;//声明式
};
c::~c() {}//定义式
因为析构函数的运作规则是是从最深层派生的class开始调用其析构,然后是每一个base class。在derived class的析构动作中编译器会创建一个对其基类析构函数的调用动作,如果没有定义,连接器会报错。
是否只要是base class就需要析构函数?
正如标题所说,只有使用了多态性的base class(具备虚函数)才需要virtual析构。
诸如上节提到的uncopyable类,其根本不需要使用virtual 析构函数。
总结
- polymorphic base class应该声明一个virtual析构。如果一个类至少有一个virtual function,把它也应该拥有一个virtual析构函数
- class 不具有多态性或者根本不作为base class,那就不要搞virtual析构。