《Effective C++》Item7:为多态基类声明虚析构函数

多态的特点是:调用相同的接口,程序的运行时行为取决于动态绑定在这个指针或者引用上的实现。例如,为了抽象出获取时间的方法,可以设计一个基类用于规定一个统一的接口,而不同的派生类用于实现各自的计时方式:

class Timer
{
public:
    Timer();
    ~Timer();

    //...
};

class UTCTimer : public Timer
{
public:
    //...
};

class AtomicTimer : public Timer
{
public:
    //...
};

此时多态的特性就凸显出来了,可以让一个父类的指针或者引用指向子类对象。这样一来,就实现了动态绑定,timer的具体行为将取决于实际绑定对象的实现。

int main()
{
    Timer *timer1 = new UTCTimer;
    Timer *timer2 = new AtomicTimer;
    //...
    delete timer1;
    delete timer2;
    return 0;
}

现在的问题出在删除对象时。多态指针的类型是Timer,然而其实际类型确是子类的实现类型。所以,在delete timer1delete timer2时,仅仅调用了基类Timer的析构函数,而并没有调用实现类的析构函数。结果时,对象将处于一个诡异的“半销毁”状态:其基类部分确实被销毁了,然而派生类部分依旧完好无损。

消除这个问题的方式很简单:给基类Timer实现一个虚析构函数。如此一来,无论是基类对象,还是派生类对象,都会被全部销毁掉:

class Timer
{
public:
    Timer();
    virtual ~Timer();
    
    //...
};

反过来看,如果一个类不含虚函数,通常就表示它不应该被继承。

实际的使用过程中,使用虚函数需要慎重。因为编译器实现虚函数确确实实是存在一些开销的。例如,为了实现虚函数和动态绑定,每个对象都必须携带某些额外信息,用于在运行期决定应该动态地调用哪个函数。这份信息通常是由一个vptr指针指出的;vptr指向一个由函数指针组成的数组,这个数组被称为vtbl。当对象调用某一个虚函数时,实际的行为取决于该对象的vptr所指的那个vtbl————编译器在其中寻找适当的函数指针。如此一来,每个对象的体积都会增加(至少需要增加一个指针的大小)。

vtbl是虚函数表,vptr是虚函数表指针。

因此,无端地将所有类的析构函数都声明为虚函数,就同永远不定义虚函数一样错误。许多人的心得是:只有当类中至少存在一个虚函数(或者纯虚函数)时,才将其析构函数声明为虚函数。

并非所有基类的设计都是用于多态用途。例如,STL中几乎所有的容器都不被设计为基类使用,更不用提多态了;还有一些类(例如input_iterator_tag),它们不是用来实现父类的成员函数的,因此它们也不需要虚析构函数。

【注意】

  • 用于多态目的的基类,必须含有一个虚析构函数。
  • 如果不是用来实现多态的基类,就不应该包含虚析构函数。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值