Effective C++ Item9 :绝不在构造和析构过程中调用virtual函数

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函数,这样的调用不会从基类下降到子类。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值