Effective C++ 条款09_不止于此

绝不在构造和析构过程中调用 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(比起当前执行构造函数和析构函数的那层)。

猜你喜欢

转载自blog.csdn.net/weixin_48033173/article/details/109003736