读书笔记《Effective C++》条款09:绝不在构造和析构过程中调用virtual函数

xiaoxiao2021-02-28  92

先看一个例子:

class Transaction {//所有交易的abstract base class public: Transaction(); virtual void logTransaction() const = 0;//做出一份因类型不同而不同的日志记录(log entry) }; class BuyTransaction : public Transaction {//derived class public: virtual void logTransaction() const; }; class SellTransaction : public Transaction {//derived class public: virtual void logTransaction() const; }; void Transaction::logTransaction() const { std::cout << "Transaction::logTransaction()" << std::endl; } void BuyTransaction::logTransaction() const { std::cout << "BuyTransaction::logTransaction()" << std::endl; } void SellTransaction::logTransaction() const { std::cout << "SellTransaction::logTransaction()" << std::endl; } Transaction::Transaction()//base class构造函数实现 { logTransaction(); }

现在,当以下这行被执行,会发生什么事:

BuyTransaction b;

无疑地会有一个BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早被调用;derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当。Transaction构造函数的最后一行调用virtual函数logTransaction,这正是引发惊奇的起点。这时候被调用的logTransaction是Transaction(base class)内的版本,不是BuyTransaction内的版本。base class构造期间virtual函数绝不会下降到derived class阶层。也可以说:在base class构造期间,virtual函数不是virtual函数。

由于base class构造函数的执行更早于derived class构造函数,当base class构造函数执行时derived class的成员变量尚未初始化。如果此期间调用的virtual函数下降至derived class阶层,要知道derived class的函数几乎必然取用local成员变量,那那些成员变量尚未初始化。这将出现不明确行为,所以C++不允许。

其实还有比上述理由更根本的原因:在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息(例如dynamic_cast和typeid),也会把对象视为base class类型。而这样的对待是合理的:这个对象内的”BuyTransaction专属成分“尚未被初始化,所以面对它们,最安全的做法就是视它们不存在。对象在derived class构造函数开始执行前不会成为一个derived class对象。

现在我们来看上面执行后的结果:是base class中的函数。

相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现出未定义值,所以C++视它们仿佛不再存在。进入base class析构函数后对象就成为一个base class对象,而C++的任何部分包括virtual函数、dynamic_cast等等也就那么看待它。

唯一避免此问题的做法是:确定你的构造函数和析构函数都没有(在对象被创建和被销毁期间)调用virtual函数,而它们调用的所有函数也都服从同一约束。

书中针对此例子有个解法办法。具体就不再罗列了,大体思路:”令derived class将必要的构造信息上传递至base class构造函数“替换之而加以弥补。这种实现方式需要注意的是不能访问使用derived class中那些处于未定义状态的成员变量。可以使用static函数实现。

要点:

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class(比起当前执行构造函数和析构函数的那层)。

转载请注明原文地址: https://www.6miu.com/read-57022.html

最新回复(0)