条款07(二):为多态基类声明virtual析构函数

127 篇文章 7 订阅
39 篇文章 3 订阅

条款07:为多态基类声明virtual析构函数

Declare destructors virtual in polymorphic base classes
接着上一篇的学习。

不要无端将析构函数声明为virtual

如果class不含virtual函数们通常表示它并不准备作为一个base class。而当一个class并不准备作为base class时,令其析构函数为virtual往往会出现问题。举个例子,定义一个表示二维坐标系下点的class:

class Point {
public:
    Point(int xCoord, int yCoord);
    ~Point();
private:
    int x, y;
}; 

对于上面的代码,如果int所占用的字节是32bits,那么这个Point对象是可以放入一个64-bit的缓存器猴子那个的。甚至,这样的一个Point对象可以被作为一个“64-bit量”传给以其他语言(C语言或者FORTRAN)所撰写的函数。
然而,如果将Point的析构函数写为virtual,情况就有所变化:

首先,如果我们想要实现一个virtual函数,对象就必须携带某些信息

  • 某些信息——用来在运行期间来决定哪一个virtual函数应该被调用

  • 这份信息则通常是由一个vptr(virtual table pointer)指针所指出。

  • vptr指向一个由函数指针构成的数组——称为vtbl(virtual table)
  • 每一个带有virtual函数的class都有一个对应的vtbl。
  • 当对象调用某一个virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl——编译器则会在其中寻找适当的函数指针。

因此,当我们如果将析构函数声明为virtual,整个对象的体积会增加:在32-bit计算机体系结构中将会占用64 bits(为了存放两个int),或者96 bits(两个int加上vptr);而在64-bit计算机体系结构中则可能会占用64~128bits,因为指针在这样的计算机体系结构占用64 bits。
因此,为Point添加一个vptr会将其对象的大小增加50%~100%!这样,Point对象也不可以和其他语言(例如C语言)内的相同声明有这一样的结构(因为其他语言的对应并没有vptr),因此,也就不再可能把它传递给(或接受自)其他语言所编写的函数,因此,也就丧失了移植性。

因此:

  • 无端地将所有classes的析构函数声明为virtual,就像从未声明它们为virtual一样,是错误的
  • 也就是说,只有当class内含至少一个virtual函数,才为它声明virtual析构函数。

而且,即使class 完全不带virtual函数,non-virtual析构函数还是有可能出现问题。举个例子,标准string不含任何virtual函数,但是有时会错误地将其作为base class:

class SpecialString : public std::string {  //错误的做法!
    ...
};

这种写法看似没有问题,但是如果在程序的某个地方无意间将一个pointer-to-SpecialString 转换成了一个pointer-to-string,然后又将这个string指针delete掉,就会立刻出现“行为不明确”的问题:

SpecialString* pss = new SpecialString("Impending Doom!");
std::string* ps;
...
ps = pss;    //SpecialString* --->  std::stirng*
...
delete ps;   //没有定义!因为现实中的*ps的SpecialString相关资源会发生泄漏
             //因为SpecialString析构函数没有被调用

相同的分析过程,适用于任何不带virtual析构函数的class,包括所有的STL容器(vector、list、set、unordered_map等等)因此,

  • 不要继承任何标准容器或者“带有non-virtual析构函数”的class!

pure virtual 析构函数

有的时候,令class带一个pure virtual析构函数,可能会比较方便。
首先,带有pure virtual函数的class为abstract(抽象)class——即不能被实例化的class,换句话说,我们不能为这种类创建对象。
由于抽象class总是试图作为一个base class来被使用,而又由于base class应该有一个virtual析构函数,并且由于pure virtual函数会导致抽象class。因此,只要我们为目标class声明一个pure virtual析构函数,这个类就变成了抽象类。举个例子:

class AWOV {
public:
    virtual ~AWOV() = 0;   //声明pure virtual析构函数

在上面的代码中,这个class有一个pure virtual函数,所以它是一个抽象class;又因为它有一个virtual析构函数,因此我们也不必担心它会出现上面所说的问题。但是这里需要注意的是:

  • 必须为这个pure virtual析构函数提供一份定义。

即:

AWOV::~AWOV() {}   //pure virtual析构函数的定义

析构函数的运作原理

析构函数的运作方式是:

  • 最深层派生(most derived)的那个class的析构函数最先被调用,然后是它的每一个base class的析构函数被调用。

在上面的例子中,编译器会在AWOV的derived classes的析构函数创建一个对~AWOV的调用动作,因此我们必须对这个函数提供一份定义。

为base classes定义一个virtual析构函数”这条规则,只适用于polymorphic(带有多态性质的)base class身上。这种base classes的设计目的就是为了用来“通过base class接口处理derived class对象”。TimeKeeper就是一个polymorphic base class,因为我们希望它可以处理多个不同的对象,即使我们只有TimeKeeper指针去指向它们。

但是,并不是所有的base class设计的目的都是为了多态。例如标准string和STL容器都不被设计作为base classes使用。另外,某些classes的设计目的是作为base class来使用,但是并不是为了多态用途。

最后:

1,polymorphic(带有多态性质的)base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。

2,Classes的设计目的如果不是作为base class使用,或不是为了具备多态性,就不该声明virtual析构函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值