《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:
本条款要阐述的重点:你不该在构造函数和析构函数中调用virtual函数,因为这样的调用不会带来你预想的结果。
构造函数
假设你有个 class 继承体系,用来塑膜股市交易如买进
卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志中也需要创建一笔适当的记录。
class Transaction { // 所有交易的基类
public:
Transaction() { logTransaction(); } //记录日志
virtual void logTransaction() const = 0; //记录交易日志, 是个虚函数
...
};
class BuyTransaction : public Transaction //购买交易,派生类
{
public:
virtual void logTransaction() const;
};
// 销售交易
class SellTransaction : public Transaction
{
public:
virtual void logTransaction() const;
};
现在执行以下代码:
BuyTransaction b;
无疑,会有一个 BuyTransaction 构造函数被调用,但是 Transaction 构造函数一定会更早的调用,因为基类会先于派生类构造。
当执行基类的构造函数时,基类的构造函数调用了虚函数logTransaction()
由于C++多态的机制,我们实际上想让基类的构造函数调用的是派生类的虚函数logTransaction()(多态:使用一个基类的指针/引用指向于派生类,且派生类重写了基类的虚函数,当用该指针/引用调用虚函数时,调用的是派生类的虚函数)
但是事实并非如此:当父类的构造函数执行,派生类此时还没有进行构造,因此基类中对logTransaction()的调用不会下降至派生类中,也就是说,此处我们在父类的构造函数中调用的实际上是基类的虚函数logTransaction(),但是由于基类中的logTransaction()函数是纯虚函数,因此程序编译错误。
在派生类执行基类的构造函数时,派生类此时还未初始化。如果此时在基类的构造函数调用虚函数,调用的实际上是基类的虚函数,对虚函数的调用不会下降到派生类中。
用一句话总结就是:在 base class 构造期间,virtual 函数不再是 virtual 函数。
析构函数
不要在析构函数中调用virtual函数的原理也是相同的:
对象在析构时会先执行自己的析构函数,接着再去执行基类的析构函数
如果在基类的析构函数中调用了虚函数,那么调用的实际上也是基类的虚函数,而不会是派生类的(因为派生类已经在先前被释放了)
如果 Transaction 有多个构造函数,每个都需要执行某些相同工作,那么避免代码重复的一个优秀做法有把共同的初始化代码(其中包括对 logTransaction的调用)放进一个初始化函数如 init 内:
class Transaction {
public:
Transaction() { init(); } // 初始化
virtual void logTransaction() const = 0; //记录交易日志, 是个虚函数
private:
void init() {
// 做一些初始化, 比如记录日志等
logTransaction();
}
};
上面在构造函数调用一个初始化函数init(),在init()中调用logTransaction()虚函数,但是也是错误的,因为logTransaction()虚函数调用的仍是基类版本的,此时编译仍报错。
而真正正确的方法则是:确定你的构造函数和析构函数都没有(在对象创建和被销毁期间)调用 virtual 函数而它们调用的所有函数都服从同一约束。
具体做法:
class Transaction {
public:
explicit Transaction(const std::string& logInfo) { logTransaction(logInfo) }
// 初始化日志信息
void logTransaction(const std::string& logInfo) const;
};
class BuyTransaction : public Transaction {
public:
// 构造自己时同时初始化基类
BuyTransaction(相关参数): Transaction(createLogString(相关参数)) {}
private:
static std::string createLogString(相关参数);
};
class SellTransaction : public Transaction {
public:
// 构造自己时同时初始化基类
SellTransaction(相关参数): Transaction(createLogString(相关参数)) {}
private:
static std::string createLogString(相关参数);
};
期待大家和我交流,留言或者私信,一起学习,一起进步!