绝不在构造和析构过程中调用 virtual 函数
假设有个 class 继承体系,用以墅模股市交易如买进、卖出的订单等等。这样的交易一定需要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当记录:
class Transaction { // 所有交易的 base class
public:
Transaction ();
virtual void logTransaction() const = 0; // 做出一份因类型不同而不同的日志记录
...
};
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; // log 此型交易
};
现在,执行下面这条语句,会发生什么事呢?
BuyTransaction buy01;
分析:
无疑地会有一个 BuyTransaction 构造函数会被调用,但首先 Transaction 构造函数一定会被更早调用。这样会发生一件很神奇的事情,Transaction 构造函数最后一行调用 virtual 函数 logTransaction,但它是Transaction 内的版本——即使目前建立的是 BuyTransaction 的对象。( base class 不太有可能定义 pure virtual 函数,所以程序无法连接)
- base class 构造期间绝不会下降到 derived classes 阶层。
取而代之的是,对象的作为就像隶属 base 类型一样。
或者这样说,在 base class 构造期间,virtual 函数不是 virtual 函数。
( virtual 函数要在 base class 声明,不太有希望,但是有可能被定义,在 derived classes 声明并定义,由 derived classes 对象调用)
在 derived class 对象的 base class 构造期间,对象的类型是 base class 而不是 derived class。
也就是说,在进行 base class 构造时,derived class 的成员不会被初始化,所以面对它们,最安全的做法就是视它们不存在。很幸运,C++ 也是这样做的。
下面讲讲解决办法:
就是确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用 virtual 函数,而它们调用的所有函数也都服从同一约束。
一种做法是在 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; // 如今是个 non-virtual 函数
...
};
Transaction::Transaction(const std::string& logInfo){
...
logTransaction(logInfo); // 现在是个 non-virtual 调用
}
class BuyTransaction: public Transaction {
public:
BuyTransaction(parameters)
: Transaction(createLogString(parameters)){...}// 将 log 信息传给 base class 构造函数
...
private:
static std::string createLogString(parameters);
}; // static 函数独立存在,只属于类本身,不属于任何对象,随着类的加载而变化
这样就很好地解决了问题。
请记住:
在构造和析构期间不要调用 virtual 函数,因为这类调用从不下降至 derived class(比起当前执行构造函数和析构函数的那层)。