0.概述
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类这一层。
1.在构造和析构期间不要调用virtual函数
1.1 显然可以观察到
举例:假设有个class继承体系,用来塑模股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志( audit log)中也需要创建一笔适当记录:
class Transaction { //所有交易的基类
public:
Transaction();
virtual void logTransaction() const = 0; // make type-dependent
// log entry
...
};
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构造函数一定会更早被调用(derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当)。
Transaction构造函数的最后一行调用virtual函数logTransaction,这时候被调用的 logTransaction是Transaction内的版本,不是 BuyTransaction内的版本——即使目前即将建立的对象类型是 BuyTransaction。
base class 构造期间virtual函数绝不会下降到derived classes阶层。
析构函数也是一样。
1.2 不显然
在上述示例中,Transaction构造函数直接调用一个virtual函数,某些编译器会发出一个警告信息。即使没有这样的警告,因为 logTransaction函数在Transaction内是个pure virtual。除非它被定义否则程序无法连接,因为连接器找不到必要的Transaction: : logTransaction实现代码。
但是有时构造或析构函数运行期间是否调用虚函数并没有那么显然。如下例,为了避免代码重复,将几个构造函数共同的初始化代码放进一个初始化函数init中:
class Transaction {
public:
Transaction()
{ init(); } // call to non-virtual...
virtual void logTransaction() const = 0;
...
private:
void init()
{
...
logTransaction(); // ...that calls a virtual!
}
};
这段代码通常不会引发编译器或连接器的警告,由于logTransaction是个纯虚函数,在其被调用时,大多数执行系统会中断程序;然而如果该函数是个非纯虚的虚函数,这样程序就会正常地向前执行,派生类对象就会调用错误版本的logTransaction。
1.3 总而言之
需要确保构造和析构函数都没有调用虚函数,他们调用的所有函数也都没有调用虚函数。
2.确保适当版本的logTraction被调用
方案:在 class Transaction内将logTransaction函数改为non-virtual,然后要求derived class构造函数传递必要信息给Transaction构造函数,而后那个构造函数便可安全地调用non-virtual logTransaction。如下:
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const; //非虚函数
...
};
Transaction::Transaction(const std::string& logInfo)
{
...
logTransaction(logInfo); //调用非虚函数
}
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction(createLogString( parameters ))//将信息传递给基类构造函数
{ ... }
...
private:
static std::string createLogString( parameters );
};
简言之,由于无法使用virtual函数从base classes向下调用,在构造期间,可以藉由“令derived classes 将必要的构造信息向上传递至 base class构造函数”替换之而加以弥补。
注:BuyTransaction内的 private static函数createLogstring的运用
- 比起在成员初始化列表内给予base class 所需数据,利用辅助函数创建一个值传给base class构造函数往往比较方便(也比较可读)。
- 令此函数为static,也就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。