【C++】不要在构造函数或析构函数内调用虚函数

  这个问题来自于《Effective C++》条款9:永远不要在构造函数或析构函数中调用虚函数 。

  假设有如下代码:

    class Transaction {// 所有交易的基类  
    public:  
     Transaction();  
     virtual void logTransaction() const = 0;//建立依赖于具体交易类型的登录项  
     ...  
    };  
    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类的构造器-派生类对象的基类部分是在派生类部分之前被构造的。 Transaction构造器的最后一行调用了虚函数logTransaction,但是奇怪的事情正是在此发生的。被调用函数 logTransaction的版本是Transaction中的那个,而不是BuyTransaction中的那个-即使现在产生的对象的类型是 BuyTransaction,情况也是如此。在基类的构造过程中,虚函数调用从不会被传递到派生类中。代之的是,派生类对象表现出来的行为好象其本身就是基类型。不规范地说,在基类的构造过程中,虚函数并没有被"构造"。

  简单的说就是,在子类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的。

  对上面这种看上去有点违背直觉的行为可以用一个理由来解释-因 为基类构造器是在派生类之前执行的,所以在基类构造器运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类, 派生类对象当然可以参照引用局部的数据成员,但是这些数据成员其时尚未被初始化。这将会导致无休止的未定义行为和彻夜的代码调试。沿类层次往下调用尚未初始化的对象的某些部分本来就是危险的,所以C++干脆不让你这样做。

  在对象的析构期间,存在与上面同样的逻辑。一旦一个派生类的析构器运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。

 

  以上是《Effective C++》的部分。其实当基类自子对象构造完毕之后,进入子类对象的构造期间调用的虚函数就是子类覆盖的版本了。即有如下代码所示:

  

    class Base  
    {  
    public:  
        Base()  
        {  
            Fuction();  
        }  
      
        virtual void Fuction()  
        {  
            cout << "Base::Fuction" << endl;  
        }  
    };  
      
    class A : public Base  
    {  
    public:  
        A()  
        {  
            Fuction();  
        }  
      
        virtual void Fuction()  
        {  
            cout << "A::Fuction" << endl;  
        }  
    };  
      
    // 这样定义一个A的对象,会输出什么?  
    A a;  

  输出为:

Base::Fuction  
A::Fuction 

 

  那么编译器是如何实现的呢?

  在基类子对象构造期间,编译器直接调用基类版本的虚函数。在子类对象构造期间,编译器直接调用基类虚函数的版本。并没有走虚机制。虽然在基类子对象的构造期间,虚表指针指向的是基类的Virtual Table,在进入子类对象构造后,虚表指针指向的是子类对象的Virtual Table。详见http://blog.csdn.net/magictong/article/details/6734241

 

参考资料:

  1. http://blog.csdn.net/hxz_qlh/article/details/14089895

  2. http://blog.csdn.net/magictong/article/details/6734241

 

转载于:https://www.cnblogs.com/vincently/p/4754206.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值