《Effective C++》Item9:不要在构造和析构函数中调用虚函数

这是一条在C++中非常重要的条款,也是C++和其他更高级的语言(例如JavaC#等)不同的地方。

假设现在我们编写了一些类,用于抽象股市中买卖股票的订单:

class Transaction
{
public:
    Transaction() 
    {
        //...
        this->logTransaction();
    }

    virtual void logTransaction() const = 0;
};

class BuyTransaction : public Transaction
{
public:
    virtual void logTransaction() const;
    //...
};

class SellTransaction : Transaction 
{
public:
    virtual void logTransaction() const;
    //...
}

现在,来思考当以下代码被执行时,发生了哪些函数调用:

int main()
{
    BuyTransaction transaction;
    return 0;
}

首先,BuyTransaction的构造函数一定会被调用,因而首先会调用Transaction的构造函数。Transaction构造函数的最后一行调用了虚函数logTransaction(),这是引发问题的地方:此时调用的是Transaction中的实现,而不是BuyTransaction中的实现。

也就是说,在构造函数中的虚函数调用绝不会绑定到子类的实现中,就好像虚函数不是虚函数一样。

其实有一个很好的理由可以解释这一现象:当调用父类的构造函数时,子类的所有成员变量都尚未被初始化,此时调用子类的成员函数必然会引发问题。所以,C++干脆拒绝掉这种调用请求。

另一方面,从编译器的角度来看,当调用父类的构造函数时,该对象的类型并不是子类,而是属于父类对象。原因是相同的,在调用父类构造函数的时候子类的成员变量一定没有初始化,因此干脆就视它们不存在好了。

相同的道理也适用于析构函数:一旦子类的析构函数开始执行,其专属的成员变量必然处于非法状态,因此也干脆视他们不存在,此时对象的类型就是其父类的类型。

虽然现在明白了这个道理,但是在实际的应用中却不能很方便地侦测到这一点,尤其是当遇到类似下面的情况时:

class Transaction
{
public:
    Transaction() { init(); }
    virtual void logTransaction() const = 0;

private:
    void init()
    {
        //...
        logTransaction();
    }
};

这段代码的结构和上一个例子基本相同,但是它却暗中为害。因此,写相关的代码时必须时刻提醒自己:在构造函数和析构函数中坚决不能调用虚函数,并且在其中调用的函数内部也不能去调用虚函数。

现在仅剩的一个问题是:如何实现类似的需求,即创建子类对象时有正确版本的成员函数被调用呢?

答案是在构造父类对象时提供足够的额外信息,即将所有的必需参数都在父类的构造函数参数中提供:

class Transaction
{
public:
    explicit Transaction(const std::string &msg)
    {
        logTransaction(msg);
    }

    void logTransaction(const std::string &msg);
};

class BuyTransaction : public Transaction
{
public:
    BuyTransaction(/* ... */) : Transaction(this->createLogMessage()) {}

private:
    std::string createLogMessage() { /* ... */ }
};

【注意】
在构造函数和析构函数中一定不能调用虚函数,因为这种调用不会绑定到子类的具体实现中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值