条款07:为多态基类声明成虚析构函数
1️⃣ 问题的引入
- 有许多种做法可以记录时间,因此,设计一个
TimeKeeper base class
和一些derived classes
作为不同的计时方法:1
2
3
4
5
6
7
8
9
10
11class TimeKeeper
{
public:
TimeKeeper () ;
~TimeKeeper () :
...
};
class AtomicClock: public TimeKeeper { ... }; //原子钟
class WaterClock: public TimeKeeper { ... }; //水钟
class WristWatch: public TimeKeeper { ... }; //腕表 - 许多客户只想在程序中使用时间,不想操心时间如何计算等细节,这应该怎么办呢 ?
- 使用
factory
函数- 返回指针指向一个计时对象。
Factory
函数会 ”返回一个base class
指针,指向新生成之derived class
对象” :1
2TimeKeeper* getTimeKeeper(); //返回一个指针,指向一个
//TimeKeeper派生类的动态分配对象
- 返回指针指向一个计时对象。
- 使用
2️⃣ 这种方式的缺点
- 根本问题
- 问题出在
getTimeKeeper
返回的指针指向一个derived class
对象(例如AtomicClock
),而那个对象却经由一个base class
指针(例如一个TimeKeeper*
指针)被删除,而目前的base class
(TimeKeeper
)有个non-virtual
析构函数。
- 问题出在
- 引发的灾难
- 当
derived class
对象经由一个base class
指针被删除,而该base class
带着一个non-virtual
析构函数,其结果未有定义 :- 实际执行时通常发生的是对象的
derived
成分没被销毁。如果getTimeKeeper
返回指针指向一个AtomicClock
对象,其内的AtomicClock
成分(也就是声明于AtomicClock class
内的成员变量)很可能没被销毁,而AtomicClock
的析构函数也未能执行起来。然而其base class
成分(也就是TimeKeeper
这一部分)通常会被销毁,于是造成一个诡异的“局部销毁”对象。
- 实际执行时通常发生的是对象的
- 当
3️⃣ 解决方法
- 给
base class
一个virtual
析构函数。 - 此后删除
derived class
对象就会销毁整个对象,包括所有derived class
成分1
2
3
4
5
6
7
8class TimeKeeper{
public:
TimeKeeper();
virtual ~TimeKeeper () :
};
TimeKeeper* ptk = getTimeKeeper ();
delete ptk; //现在,行为正确。
4️⃣ 析构函数声明为虚函数的时机
- 定义
- 如果
class
不含virtual
函数,通常表示它并不意图被用做一个base class
。 - 当
class
不企图被当作base class
,令其析构函数为virtual
往往是个馊主意。
- 如果
- 案例引入 如果
1
2
3
4
5
6
7
8class Point
{
public:
Point(int xCoord, int yCoord) ;
~Point () ;
private:
int x, y;
};int
占用 32 bits,那么Point
对象可塞入一个64-bit 缓存器中。然而当Point
的析构函数是virtual
,形势起了变化。 - 🤓虚函数原理
- 欲实现出
virtual
函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual
函数该被调用。 - 这份信息通常是由一个所谓
vptr
(virtual table pointer
)指针指出。 vptr
指向一个由函数指针构成的数组,称为vtbl
(virtual table
);每一个带有virtual
函数的class
都有一个相应的vtbl
。当对象调用某一virtual
函数,实际被调用的函数取决于该对象的vptr
所指的那个vtbl
——编译器在其中寻找适当的函数指针。
- 欲实现出
- 🥵额外的开销
- 如果
Point class
内含virtual
函数,其对象的体积会增加:- 在 32-bit 计算机体系结构中将占用64bits(存放两个
ints
)至96 bits(两个ints
加上vptr
); - 在 64-bit 计算机体系结构中可能占用64
128 bits,因为指针在这样的计算机结构中占 64bits 。因此,为100% !Point
添加一个vptr
会增加其对象大小达 50%
- 在 32-bit 计算机体系结构中将占用64bits(存放两个
- 如果
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!