析构函数和构造函数关于虚函数的事

effective c++里面的一些知识

假设构造函数里面包含virtual函数的情况

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; //怎样实现这种类型交易的登录?  

};

这段代码来自《Effecitive C++》条款09,当声明一个BuyTransaction对象的时候,首先Transaction的构造函数会被调用,从而其virtual函数也被调动,这里就是引发惊奇的起点。这时候被调用的logTransaction是Transaction的版本,而不是派生类BuyTransaction的版本。

我们再看一段代码。

    class Base
    {
    public:
        Base()
        {
            Fuction();
        }
     ~Base()
     {
          Fuction1();
     }

        virtual void Fuction()
        {
            cout << "Base::Fuction" << endl;
        }
        virtual void Fuction1()
        {
            cout << "Base::Fuction1" << endl;
        }
    };

class A : public Base
    {
    public:
        A()
        {
            Fuction();
        }
       ~A()
       {
         Fuction1();
       }
        virtual void Fuction()
        {
            cout << "A::Fuction" << endl;
        }
        virtual void Fuction1()
        {
            cout << "A::Fuction1" << endl;
        }
    };
#include<iostream>
using namespace std;
int main()
    {
        A a;
        cout<<1<<endl;

    }

当声明一个派生类对象,会发生什么呢?

程序输出为:

Base::Fuction
A::Fuction
1
A::Fuction1
Base::Fuction1

A类继承自Base,在Base中有虚函数Function,并且有代码,同时构造(析构)函数调用了虚函数,此时对象a先调用基类Base的构造函数,从而调用了Function函数,此时调用的还是基类的Functio函数,所以并没有实现多态的功能,析构函数先调用了A类的析构函数,从而调用了自己的function1,再调用了基类的function1。

总结

在基类的构造过程中,虚函数调用从不会被传递到派生类中。派生类对象表现出来的行为好象其本身就是基类型。不规范地说,在基类的构造过程中,虚函数并没有被”构造”。也就是说,在派生类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的。

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

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

所以,析构函数和构造函数不能调用虚函数,从语法上讲,调用完全没有问题, 但是从效果上看,往往不能达到需要的目的。 派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。 同样,进入基类析构函数时,对象也是基类类型。