重点:你不该在构造函数和析构函数期间调用virtual函数。
例:
- class Transaction//所有交易的基类
- {
- public:
- Transaction();
- virtual void logTransaction() const = 0;//做出一份因类型不同而不同
- //的日志记录
- ...
- };
- Transaction::Transaction()//基类构造函数的实现
- {
- ...
- logTransaction();//日记记录这笔交易
- }
- class BuyTransaction : public Transaction//继承类
- {
- public;
- virtual void logTransaction() const;//日志记录此交易
- ...
- };
- class SellTransaction : public Transaction
- {
- public:
- virtual void logTransaction() const;//日志记录此交易
- };
- //执行以下
- BuyTransaction b;
有一个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早被调用,Transaction构造函数里调用了虚拟函数logTransaction,这正式引起惊奇的地方。这时候被调用的logTransaction是Transaction内地版本,不是BuyTransaction内的版本。基类构造期间虚拟函数绝不会下降到继承类阶层,取而代之的是,对象的作为就像隶属基类类型一样,在基类构造期间,虚拟函数不是虚拟函数。
由于基类构造函数的执行更早与继承类构造函数,当基类构造函数执行时继承类的成员变量尚未初始化。如果此期间调用的虚拟函数下降到继承阶层,要知道继承类的函数几乎必然取用local成员变量,而这些变量尚未初始化。“要求使用对象内部尚未初始化的成分”是危险的。
根本原因是:在继承类对象的基类构造期间,对象的类型是基类而不是继承类。不只虚拟函数会被编译器解析至基类,若使用运行期类型信息,也会把对象视为基类类型。本例之中,当Transaction构造函数正执行起来打算初始化BuyTransaction对象内的基类成分时,该对象的类型是Transaction。
对象在继承类构造函数开始执行前不会成为一个继承类对象。
相同的道理也是用于析构函数。一旦继承类析构函数开始执行,对象内的继承类成员变量便呈现为定义值,所有C++视它们仿佛不再存在。进入基类析构函数后对象就成为一个基类对象,而C++的任何部分包括虚拟函数,dynamic_casts等等也就那么看待它。
如果类有多个构造函数,每个都需要执行某些相同的工作,那么避免代码重复的一个优秀做法是把共同的初始代码放进一个初始化函数如init内。
但你如何确保每次一个Transaction继承体系上的对象被创建,就会有适当版本中的logTransaction被调用呢?很显然,在Transaction构造函数内对着对象调用虚拟函数是一种错误做法。
解决办法 一种是在类 Transaction内将logTransaction函数改为non-virtual,然后要求继承类构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全调用non-virtual logTransaction。
- class Transatcion
- {
- public:
- explicit Transaction(cosnt std::string& logInfo);
- void logTransaction(const std::string& logInfo) const;//non-virtual
- ...
- };
- Transaction::Transaction(const std::string& logInfo)
- {
- ...
- logTransaction(logInfo);//non-virtual调用
- }
- class BuyTransaction:public Transaction
- {
- public:
- BuyTransaction(parameters)
- : Transaction(createLogString(parameters))
- {}//将log信息传递给基类构造函数
- ...
- private:
- static std::string createLogString(parameters);
- };
换句话说由于你无法使用虚拟函数从基类向下调用,在构造期间,你可以藉由“令继承类将必要的构造信息向上传递至基类构造函数”替换之而加以弥补。
请注意本例之BuyTransaction内的private static 函数createLogString的运用,是的,比起在成员初始化列表内给予基类所需数据,利用辅助函数创建一个值给基类构造函数往往比较方便。令此函数为static,也就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。
在构造和析构期间不要调用虚拟函数,因为这类调用从不下降至继承类。