对于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. 子类构造函数体调用
(注意一点,初始化列表内的数据不按书写顺序,而是按类内部的定义顺序)