条款06:绝不在构造和析构过程中调用虚函数
1️⃣ 案例引入
- 假设你有个
class
继承体系,用来塑模股市交易如买进、卖出的订单等等。这样
的交易一定要经过审计,所以每当创建一个交易对象,在审计日志(audit log)中也
需要创建一笔适当记录。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24class Transaction { //所有交易的 base class
public:
Transaction();
virtual void logTransaction() const = 0; //做出一份因类型不同而不同
//的日志记录(log entry)
};
Transaction::Transaction() // base class构造函数之实现
{
···
logTransaction(); // 最后动作是志记这笔交易
};
class BuyTransaction: public Transaction //derived class
{
public:
virtual void logTransaction() const; //志记(log)此型交易
};
class SellTransaction: public Transaction //derived class
{
public:
virtual void logTransaction() const; //志记(1og)此型交易
}; - 现在,当以下这行被执行,会发生什么事 ?
1
BuyTransaction b;
- 🥵执行过程
BuyTransaction
构造函数被调用- 首先
Transaction
构造函数一定会更早被调用———derived class
对象内的base class
成分会在derived class
。自身成分被构造之前先构造妥当。 Transaction
构造函数的最后一行调用virtual
函数logTransaction
,这正是引发惊奇的起点。- 这时候被调用的
logTransaction
是Transaction
内的版本,不是BuyTransaction
内的版本———即使目前即将建立的对象类型是BuyTransaction
。是的,baseclass
构造期间virtual
函数绝不会下降到derived classes
阶层。取而代之的是,对象的作为就像隶属base
类型一样。 - 非正式的说法或许比较传神:在
base class
构造期间,virtual
函数不是virtual
函数。这一似乎反直觉的行为有个好理由。由于base class
构造函数的执行更早于derived class
构造函数,当base class
构造函数执行时derived class
的成员变量尚未初始化。如果此期间调用的virtual
函数下降至derived classes
阶层,要知道derived class
的函数几乎必然取用local
成员变量,而那些成员变量尚未初始化。
- 这时候被调用的
- 🤓根本原因
- 在
derived class
对象的base class
构造期间,对象的类型是base class
而不是derived class
。 - 不只
virtual
函数会被编译器解析至base class
,若使用运行期类型信息(例如dynamic_cast
和typeid
),也会把对象视为base class
类型。
- 在
2️⃣ 同理析构函数
- 原理
derived class
析构函数开始执行,对象内的derived class
成员变量便呈现未定义值,所以C++视它们仿佛不再存在。- 进入
base class
析构函数后对象就成为一个base class
对象,而C++的任何部分包括virtual
函数、dynamic_casts
等等也就那么看待它。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 GYu的妙妙屋!