【复读EffectiveC++09】条款09:绝不在构造和析构过程中调用virtual函数

条款09:绝不在构造和析构过程中调用virtual函数

此条款,针对的是在构造函数和析构函数中调用虚函数的问题,其内容包括:
a、为什么不要在构造函数/析构函数调用虚函数
b、有什么方法改进

一、为什么不要在构造函数/析构函数调用虚函数

原书中的例子为:

  • 创建一个基类Transaction和两个派生类BuyTransaction、SellTransaction
  • 基类Transaction有一个虚函数logTransaction(),其会在构造函数中调用虚函数
  • 使用基类指针构造一个派生类对象
// 表示不同的事务交易
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;
};

Transaction *pb = new BuyTransaction;

这种情况分析一下,因为:
a、基类的指针构造了一个派生类对象,那么派生类在进行构造时会先执行基类的构造函数
b、基类的构造函数调用了虚函数 logTransaction()
因此造成了想要执行派生类的 logTransaction(),但实际上这个时候派生类还未完成构造,变成了想要执行基类的logTransaction(),可这是个虚函数,编译器是不会允许的
在这里插入图片描述
析构函数也是如此,派生类已经析构了,基类再析构,造成调用基类的虚函数的实际效果

二、有什么方法改进

总会有人,我偏偏就要这样做,有没有方法?
作为万能的程序猿,就要拿出解决方案,等效即可,即提交日志的行为因为类的不同而发生改变

原书给了一个错误的解决示例。

class Transaction {
public:
    Transaction() { init(); }  // 初始化
 
    virtual void logTransaction() const = 0; //记录交易日志, 是个虚函数
private:
    void init() { 
        // 做一些初始化, 比如记录日志等
        logTransaction(); 
    }
};

此问题看上去是等效了,但实际还是执行基类的logTransaction(),那个虚函数,编译器只会两手一摊,给你报错

正确的解决方案是:

  • 在 Transaction 类中将 logTransaction() 函数改为 non-virtual 函数
  • 要求子类构造函数传递必要信息给 Transaction 构造函数

取原书例子:

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(相关参数);
};

这一做法,初读的时候还是有些迷糊的,后来仔细分析了一下才有些了然。
个人认为,此做法针对的是 “ 先基类构造,后派生类构造” 的虚函数问题,将其变为 “ 先派生类构造,后基类构造 ” 的普通成员函数传参处理,以此达到原来大致相同的效果

三、总结

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

此部分,个人觉得还有一个点要关注,在于解决问题的一个思路,需求不会变,就要确定目的,制造等效方法,此例,给出的 调整冲突顺序 就是制造整体等效的方法之一,这些都是说起来简单,做起来难的地方

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值