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

对于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布局操作符把类布局到我们的空间上,这时类的构造过程就少了allocatedeallocate。如果你对new布局操作符不熟悉,可以找一本基础C++书看一下,一般都会有的,如果没有就换一本(记得把原来那本烧掉J)。这里使用new布局操作符是为了查看我们的内存发生了什么,来了解类构造的过程。
    下图是在vs2010上断下的情形,先找到buff1的地址,之后把这个地址写到内存查看中,这样做是因为当buff1出了作用域时仍然可以监视内存的变化。

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

先跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

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

之后跳出来到下图

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 这是把参数字串构造成

string对象的构造函数

跳出。

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 又回到主函数

再跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

再跟进。

此时发生了两件事,

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

跳出。

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

String类构造函数

跳出

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

到达基类构造函数体。

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

跳出

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

到达子类构造函数体外。

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

内存发生变化

再跟进

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

程序到达string参数构造函数

再跟进

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

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

 

C++带有虚函数的单继承类的构造过程探索,msvc和gcc编译器[原创] - saturnman - 一路

 

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

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

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

3.基类设置vptr

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

5. 基类函数体调用

6. 子类设置vptr

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

8. 子类构造函数体调用

(注意一点,初始化列表内的数据不按书写顺序,而是按类内部的定义顺序)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值