您的位置:首页 > 其它

探索 带有虚函数的单继承的类层次的子类对象的构造过程

2013-05-11 16:15 323 查看
对于C++带有虚函数的单继承类的子类对象的构造过程,我这里通过调试器看一下到底构造过程中发生了什么。

#include<iostream>

#include<string>

using namespace std;

//Function to show binary info of an object

typedef unsigned char* pointer_type;

void show_byte(pointer_type pointer,int length);

void show_byte(pointer_type pointer,int length)

{

    cout<<"address of object:"<<hex<<(int)pointer<<endl;

    cout<<"var size in byte:"<<dec<<length<<endl;

    for(int i=0;i<length;++i)

    {

        cout<<"|address: 0x"<<hex<<(int)(pointer+i)<<" | value:0x"<<(int)(*(pointer+i))<<" |"<<endl;

    }

    cout<<endl;

}

class Base

{

public:

    Base()

    {

        cout<<"In Base::constructor."<<endl;

        cout<<"Constructing "<<*m_pObj_Name<<endl;

        cout<<"binary info:"<<endl;

        show_byte((pointer_type)this,sizeof(*this));

    }

    Base(const string& name):

    flag_var(0x12345678),

        m_pObj_Name(new string(name))

    { 

        cout<<"In Base::constructor."<<endl;

        cout<<"Constructing "<<*m_pObj_Name<<endl;

        cout<<"binary info:"<<endl;

        show_byte((pointer_type)this,sizeof(*this));

    }

    void Util()

    {

        virtual_func1();

    }

    virtual ~Base()  {delete m_pObj_Name;}

    const string& GetName()

    {

        return *(this->m_pObj_Name);

    }

private:

    int flag_var;

    string* m_pObj_Name;

    virtual void pure_virtual_1() = 0;//pure virtual function

    virtual void pure_virtual_2() = 0;//pure virtual function

    virtual void virtual_func1()      //normal virtual function

    {

        cout<<"virtual function in Base."<<endl;

    }

};

class Derived: public Base

{

public:

    Derived(string name): //initialize list

      //defined for testing invoke order of initialize list and base constructor

      a("10"),

      Base(name)

      {

          cout<<"In Derived::constructor."<<endl;

          cout<<"binary info:"<<endl;

          show_byte((pointer_type)this,sizeof(*this));

      }

      void Util()

      {

          virtual_func1();

      }

      virtual ~Derived()  {}

private:

    virtual void pure_virtual_1() { printf("pure_virtual_1() in drived\n"); }

    virtual void pure_virtual_2() { printf("pure_virtual_2() in drived\n"); }

    virtual void virtual_func1()

    {

        cout<<"virtual function in Derived."<<endl;

    }

    //debug

    string a;

};

int main()

{

    char buff1[1024];

    char buff2[1024];

    Derived* d1 = new(buff1) Derived("Derived_1");

    Base* b1 = d1;

    cout<<"After construct:"<<d1->GetName()<<endl;

    show_byte((pointer_type)d1,sizeof(d1));

    d1->Util();

    b1->Util();

    Derived* d2 = new(buff2) Derived("Derived_2");

    Base* b2 = d2;

    cout<<"After construct:"<<d2->GetName()<<endl;

    show_byte((pointer_type)d2,sizeof(d2));

    d2->Util();

    b2->Util();

    d1->~Derived();

    d2->~Derived();

    return 0;

}

为了便于理解本文,我们再回顾一下类的基本构造与销毁过程,可分为如下四步:1.allocate 2.construct 3.destruct 4.deallocate 对于这四步并不一定是连续的,可以先申请空间(allocate),在等待一定时机或是某个条件触发后,再进行类的构造,典型的例子stl的容器类,比如vector,对其调用reserve()函数后,其空间增大,但是并没有在空间上调用默认构构函数对容器元素进行构造,只有当push_back()之类的函数调用发生时再构造相应的函数,这样可以极大节约进行时开销。好,类的一般构造过程就回顾到这里,为了调式类的构造过程,我们在此使用了预先申请好的内存空间,之后利用new布局操作符把类布局到我们的空间上,这时类的构造过程就少了allocate和deallocate。如果你对new布局操作符不熟悉,可以找一本基础C++书看一下,一般都会有的,如果没有就换一本(记得把原来那本烧掉J)。这里使用new布局操作符是为了查看我们的内存发生了什么,来了解类构造的过程。
    下图是在vs2010上断下的情形,先找到buff1的地址,之后把这个地址写到内存查看中,这样做是因为当buff1出了作用域时仍然可以监视内存的变化。




 

则开始,buff1的内存地址放入监视。

先跟进



 由于我们的内存是事先分配好的,类只要布局上去就可以了,这个内联函数很简单,直接返回地址,其实在编译后这个函数不存在代码,只是一个地址,不要对此感到奇怪。
之后跳出来到下图



 

此时回到主函数,再跟进。



 这是把参数字串构造成
string对象的构造函数
跳出。



 又回到主函数
再跟进



 

子类构造函数【这里还没有运行子类构造函数体任何代码】

跟进



 

先调用基类构造函数,【这里还没有运行基类构造函数任何代码】

再跟进。

此时发生了两件事,

 



 

内存发生变化,vptr被写入,程序到达内存申请函数外

 



 

跳出。

 



 

又到达子类构造函数外,此时参数string构造完成

再跟进

 



 

String类构造函数

跳出

 



 

跟进,内存发生变化,m_pObj_Name值发生变化



 
到达基类构造函数体。

我们发现是在vptr值改变完成后再对初始化列表内的值进行初始化的。

跳出



 
到达子类构造函数体外。

再跟进

 



 

内存发生变化

再跟进

 



 

程序到达string参数构造函数

再跟进



再进一步,程序到达子类函数体

 



 

到此我们得到如下构造顺序

1.构造子类构造函数的参数

2.子类调用基类构造函数

3.基类设置vptr

4.基类初始化列表内容进行构造

5. 基类函数体调用

6. 子类设置vptr

7. 子类初始化列表内容进行构造

8. 子类构造函数体调用

(注意一点,初始化列表内的数据不按书写顺序,而是按类内部的定义顺序)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐