1.在构造函数中调用virtual函数的后果:
设想下面的场景:假设有个处理时是继承体系,用来模拟故事交易:买进、卖出的订单等等。这样的订单一定要经过审计,所以每当创建一个交易对象,在审计日志中也要创建一笔记录。于是,有下面的设计:
class Transaction { //所有交易的抽象基类
public:
Transaction();
virtual void logTransaction() const = 0;//提供接口给子类根据需要给出接口的不同实现
};
Transaction::Transaction() {
logTransaction(); //标记这份交易
}
void Transaction::logTransaction() const {
cout << "Base Transaction" << endl;
}
class BuyTransaction : public Transaction {
public:
virtual void logTransaction() const //标记此类型的交易
{
cout << "Buy Transaction" << endl;
}
};
class SellTransaction : public Transaction {
public:
virtual void logTransaction() const //标记此类型的交易
{
cout << "Sell Transaction" << endl;
}
};
int main()
{
BuyTransaction b;
return 0;
}
上述代码中,在Transaction基类的构造函数中调用了virtual函数logTransaction,并期望该函数根据声明对象的类型调用对应的标记类型函数。执行上述代码,发现事实并非如此:
尽管我们声明的对象时BuyTransaction,但最终调用的却是基类Transaction中定义的logTransaction函数。
这个违反意图的结果在于:base class的构造函数的执行早于Derived class的构造函数,当base class的构造函数执行时,Derived class的成员变量尚未初始化,如果在此期间调用的virtual函数下降到Derived class阶层,要知道Derived class的函数必须要取用local成员变量,而那些成员变量尚未初始化,这将导致不明确行为,所以C++直接调用了基类的函数来执行。
2. 析构函数中调用virtual函数
一旦Derived class析构函数开始执行,对象内的Derived class成员变量呈现未定义值,C++视它们不存在,进入Base class的析构函数后,对象就变成一个base class对象,C++的virtual函数、dynamic_casts在此时都会将Derived class对象看做Base class对象。因此,在析构函数中调用virtual函数也是不可行的。
3.侦测“析构函数或构造函数在运行期间是否调用virtual函数”
如果Transaction类有多个构造函数,每个都需执行相同的标记交易记录的工作,为避免代码重复,将共同的初始化代码放进一个初始化函数init中。这样的设计方案实现如下:
class Transaction { //所有交易的抽象基类
public:
Transaction() { //构造函数中改调用non-virtual函数,但non-virtual函数中调用virtual函数
init();
}
virtual void logTransaction() const = 0;//提供接口给子类根据需要给出接口的不同实现
void init();
};
void Transaction::init() {
logTransaction();
}
void Transaction::logTransaction() const {
cout << "Base Transaction" << endl;
}
这个代码中在构造函数中调用了non-virtual函数init,但是函数init中却调用了virtual函数logTransacion,此时,也会产生与上述直接调用virtual函数一样的恶果。
4.确保Transaction继承体系上的对象创建时,都有正确版本的logTransaction函数被调用
解决方案:
在class Transaction内将logTransaction函数改为non-virtual,要求Derived class构造函数传递必要信息给Transaction构造函数,然后那个构造函数便可安全调用non-virtual的logTransaction函数。
方案的代码实现如下:
class Transaction { //所有交易的抽象基类
public:
explicit Transaction(const std::string& logInfo) {
logTransaction(logInfo);
}
void logTransaction(const std::string& logInfo) const;//修改为non-virtual函数
};
class BuyTransaction : public Transaction {
public:
BuyTransaction(parameters) :Transaction(createLogString(parameters)) {
}
private:
static std::string createLogString(parameters);
};
这个方案中,无法使用virtual函数从base class向下调用,在构造期间,可以领Derived class 将必要的构造信息向上传递至Base class的构造函数。
5.总结
在析构函数和构造函数期间不要调用virtual函数,这样的调用不会从基类下降到子类。