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

0.概述

在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类这一层。

1.在构造和析构期间不要调用virtual函数

1.1 显然可以观察到

举例:假设有个class继承体系,用来塑模股市交易如买进、卖出的订单等等。这样的交易一定要经过审计,所以每当创建一个交易对象,在审计日志( audit log)中也需要创建一笔适当记录:

class Transaction { //所有交易的基类
public: 
    Transaction();
    virtual void logTransaction() const = 0; // make type-dependent
    // log entry
    ...
};
Transaction::Transaction() //基类构造函数
{ 
    ...
    logTransaction(); //记录交易
} 
class BuyTransaction: public Transaction { //派生类
public:
    virtual void logTransaction() const; //记录此类型交易
    ...
};
class SellTransaction: public Transaction { //派生类
public:
    virtual void logTransaction() const; //记录此类型交易
...
};

执行

BuyTransaction b;

会有一个 BuyTransaction构造函数被调用,但首先Transaction构造函数一定会更早被调用(derived class对象内的base class成分会在derived class自身成分被构造之前先构造妥当)。

Transaction构造函数的最后一行调用virtual函数logTransaction,这时候被调用的 logTransaction是Transaction内的版本,不是 BuyTransaction内的版本——即使目前即将建立的对象类型是 BuyTransaction。

base class 构造期间virtual函数绝不会下降到derived classes阶层。

析构函数也是一样。

1.2 不显然

在上述示例中,Transaction构造函数直接调用一个virtual函数,某些编译器会发出一个警告信息。即使没有这样的警告,因为 logTransaction函数在Transaction内是个pure virtual。除非它被定义否则程序无法连接,因为连接器找不到必要的Transaction: : logTransaction实现代码。

但是有时构造或析构函数运行期间是否调用虚函数并没有那么显然。如下例,为了避免代码重复,将几个构造函数共同的初始化代码放进一个初始化函数init中:

class Transaction {
public:
    Transaction()
    { init(); } // call to non-virtual...
    virtual void logTransaction() const = 0;
    ...
private:
    void init()
    {
    ...
    logTransaction(); // ...that calls a virtual!
    }
};

这段代码通常不会引发编译器或连接器的警告,由于logTransaction是个纯虚函数,在其被调用时,大多数执行系统会中断程序;然而如果该函数是个非纯虚的虚函数,这样程序就会正常地向前执行,派生类对象就会调用错误版本的logTransaction。

1.3 总而言之

需要确保构造和析构函数都没有调用虚函数,他们调用的所有函数也都没有调用虚函数

2.确保适当版本的logTraction被调用

方案:在 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; //非虚函数
    ...
};
Transaction::Transaction(const std::string& logInfo)
{
    ...
    logTransaction(logInfo); //调用非虚函数
} 
class BuyTransaction: public Transaction {
public:
    BuyTransaction( parameters )
    : Transaction(createLogString( parameters ))//将信息传递给基类构造函数
    { ... }
    ... 
private:
    static std::string createLogString( parameters );
};

简言之,由于无法使用virtual函数从base classes向下调用,在构造期间,可以藉由“令derived classes 将必要的构造信息向上传递至 base class构造函数”替换之而加以弥补。

:BuyTransaction内的 private static函数createLogstring的运用

  • 比起在成员初始化列表内给予base class 所需数据,利用辅助函数创建一个值传给base class构造函数往往比较方便(也比较可读)。
  • 令此函数为static,也就不可能意外指向“初期未成熟之BuyTransaction对象内尚未初始化的成员变量”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值