条款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的妙妙屋!
