之前其实看过深度探索C++对象模型的了。现在只要是重温一遍,把自己的感受记录下来,留着以后能够快速实现查看和温故而知新。
在C++中,有两种class data members:static 和non-static,以及三种class member functions:static、non-static和virtual。已知下面这个class Point 声明,直接上代码吧。
1
2 3 4 5 6 7 8 9 10 11 12 13 |
class Point
{ public: Point( float xval); //构造函数 virtual ~Point(); //析构虚函数 float x() const; //成员函数 static int PointCount(); //静态函数 protected: virtual ostream & print(ostream &os) const; //友元函数 float _x; //变量 static int _point_count; //静态变量 }; |
另外一种模型是表格驱动对象模型。这种模型的针对member data和member functions 进行指针索引,这种模型同样没有被C++采纳,但是这种观念是解决Virtual functions的一个有效方案。模型图像如下所示:
C++对象模型
下面我们来看看C++被采用的实现模型。直接上图吧。
根据上图,我们知道C++对象模型是从简单对象模型派生而来的,并对内存空间和存取时间做了优化。在此模型中,Non-static data members 被配置于每一个class Object 之内,static data members则被存放在个别的class object之外,static 和non-static function members 也被放在个别的class object之外。Virtual functions 则以两个步骤支持之:
1、每一个class产生一堆指向Virtual functions的指针,放在表格之中。这个表格被称为Virtual tables。
2、每一个class object 被安插一个指针,指向相关的Virtual table。通常这个指针被称为vptr。vptr的设定和重置都由每一个class 的constructor 、destructor和copy assignment运算符自动完成。每个class关联的type-info object (用以支持RTTI,runtime type identification)也经由Virtual table被指出来,通常放在表格的第一个slot。RTTI是为多态而生成的信息,所以只有具有虚函数的对象在会生成。
这种模型的优缺点是显而易见的,有点在于它的空间和存取时间的效率,主要缺点在于,如果应用程序代码本身未曾改变,但是所用到的class object的non-static data members 有所修改,那么程序必修重新编译。
C++对象模型的验证实现:
基本类:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
class Base
{ public: Base( int); virtual ~Base( void); int getIBase() const; static int instanceCount(); virtual void print() const; protected: int iBase; static int count; }; //初始化静态成员数据 int Base::count = 0; //静态方法 int Base::instanceCount() { cout<< "Base::instanceCount()\tcount地址: " << &count << endl; return count; } Base::Base( int i) { iBase = i; count++; cout<< "Base::Base()"<<endl; } Base::~Base( void) { cout<< "Base::~Base()" <<endl; } int Base::getIBase() const { cout<< "实际iBase地址:" << &iBase << endl; return iBase; } |
测试代码
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include<iostream>
#include<string> #include <typeinfo> #include <windows.h> using std::type_info; using namespace std; typedef unsigned long DWORD; struct TypeDescriptor { DWORD ptrToVTable; DWORD spare; char name[ ]; }; struct RTTICompleteObjectLocator { DWORD signature; //always zero ?0 DWORD offset; //offset of this vtable in the complete class DWORD cdOffset; //constructor displacement offset4 struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class int * ptr; //struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy }; |
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void test_base_model()
{ Base b1( 1000); cout << "对象b1的起始内存地址:" << &b1 << endl; cout << "type_info信息:" << (( int*)*( int*)(&b1) - 1) << endl; RTTICompleteObjectLocator str= *((RTTICompleteObjectLocator*)*(( int*)*( int*)(&b1) - 1)); //abstract class name from RTTI string classname(str.pTypeDescriptor->name); classname = classname.substr( 4,classname.find( "@@")- 4); cout << classname <<endl; cout << "虚函数表地址:\t\t\t" << ( int*)(&b1) << endl; cout << "虚函数表 — 第1个函数地址:\t" << ( int*)*( int*)(&b1) << "\t即析构函数地址:" << ( int*)*(( int*)*( int*)(&b1)) << endl; cout << "虚函数表 — 第2个函数地址:\t" << (( int*)*( int*)(&b1) + 1) << "\t"; typedef void(*Fun)( void); Fun pFun = (Fun)*((( int*)*( int*)(&b1)) + 1); pFun(); b1.print(); cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int*)(&b1) + 1) << "\t通过地址取值iBase的值:" << *(( int*)(&b1) + 1) << endl; cout << "Base::getIBase(): " << b1.getIBase() << endl; b1.instanceCount(); cout << "静态函数instanceCount地址: " << b1.instanceCount << endl; } |
根据C++对象模型,我们可以进行理论上的分析:
实例化对象b1的起始内存地址,即虚函数表地址。
l 虚函数表的中第1个函数地址是虚析构函数地址;
l 虚函数表的中第2个函数地址是虚函数print()的地址,通过函数指针可以调用,进行验证;
l 推测数据成员iBase的地址,为虚函数表的地址 + 1,((int*)(&b1) +1);
l 静态数据成员和静态函数所在内存地址,与对象数据成员和函数成员位段不一样;
下面是测试代码输出:(从下面2个图验证了,上面的观点。)
注意:本测试代码及后面的测试代码中写的函数地址,是对应虚函数表项的地址,不是实际的函数地址。
===========================================================================
C++对象模型中加入单继承
不管是单继承、多继承,还是虚继承,如果基于“简单对象模型”,每一个基类都可以被派生类中的一个slot指出,该slot内包含基类对象的地址。这个机制的主要缺点是,因为间接性而导致空间和存取时间上的额外负担;优点则是派生类对象的大小不会因其基类的改变而受影响。
如果基于“表格驱动模型”,派生类中有一个slot指向基类表,表格中的每一个slot含一个相关的基类地址(这个很像虚函数表,内含每一个虚函数的地址)。这样每个派生类对象汗一个bptr,它会被初始化,指向其基类表。这种策略的主要缺点是由于间接性而导致的空间和存取时间上的额外负担;优点则是在每一个派生类对象中对继承都有一致的表现方式,每一个派生类对象都应该在某个固定位置上放置一个基类表指针,与基类的大小或数量无关。第二个优点是,不需要改变派生类对象本身,就可以放大,缩小、或更改基类表。
不管上述哪一种机制,“间接性”的级数都将因为集成的深度而增加。C++实际模型是,对于一般继承是扩充已有存在的虚函数表;对于虚继承添加一个虚函数表指针。
无重写的单继承
无重写,即派生类中没有于基类同名的虚函数。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class Derived :
public Base { public: Derived( int); virtual ~Derived( void); virtual void derived_print( void); protected: int iDerived; }; Derived::Derived( int i): Base( 0) { iDerived = i; cout<< "Derived::Derived()"<<endl; } Derived::~Derived( void) { cout<< "Derived::~Derived()"<<endl; } void Derived::derived_print() { cout<< "Derived::derived_print()"<<iDerived<<endl; } |
Base、Derived的类图如下所示:
Base的模型跟上面的一样,不受继承的影响。Derived不是虚继承,所以是扩充已存在的虚函数表,所以结构如下图所示:
为了验证上述C++对象模型,我们编写如下测试代码。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void test_single_inherit_norewrite()
{ Derived d( 9999); cout << "对象d的起始内存地址:" << &d << endl; cout << "type_info信息:" << (( int*)*( int*)(&d) - 1) << endl; RTTICompleteObjectLocator str= *((RTTICompleteObjectLocator*)*(( int*)*( int*)(&d) - 1)); //abstract class name from RTTI string classname(str.pTypeDescriptor->name); classname = classname.substr( 4,classname.find( "@@")- 4); cout << classname <<endl; cout << "虚函数表地址:\t\t\t" << ( int*)(&d) << endl; cout << "虚函数表 — 第1个函数地址:\t" << ( int*)*( int*)(&d) << "\t即析构函数地址" << endl; cout << "虚函数表 — 第2个函数地址:\t" << (( int*)*( int*)(&d) + 1) << "\t"; typedef void(*Fun)( void); Fun pFun = (Fun)*((( int*)*( int*)(&d)) + 1); pFun(); d.print(); cout << endl; cout << "虚函数表 — 第3个函数地址:\t" << (( int*)*( int*)(&d) + 2) << "\t"; pFun = (Fun)*((( int*)*( int*)(&d)) + 2); pFun(); d.derived_print(); cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int*)(&d) + 1) << "\t通过地址取得的值:" << *(( int*)(&d) + 1) << endl; cout << "推测数据成员iDerived地址:\t" << (( int*)(&d) + 2) << "\t通过地址取得的值:" << *(( int*)(&d) + 2) << endl; } |
有重写的单继承
派生类中重写了基类的print()函数。
代码实现如下:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
class Derived_Overrite :
public Base { public: Derived_Overrite( int); virtual ~Derived_Overrite( void); virtual void print( void) const; protected: int iDerived; }; Derived_Overrite::Derived_Overrite( int i): Base( 0) { iDerived = i; cout << "Derived_Overrite::Derived_Overrite()" << endl; } Derived_Overrite::~Derived_Overrite( void) { cout << "Derived_Overrite::~Derived_Overrite()" << endl; } void Derived_Overrite::print( void) const { cout << "Derived_Overrite::print()£? iDerived " << iDerived << endl; } |
Base、Derived_Overwrite的类图如下所示:
重写print()函数在虚函数表中表现如下:
为了验证上述C++对象模型,我们编写如下测试代码。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
void test_single_inherit_rewrite()
{ Derived_Overrite d( 111111); cout << "对象d的起始内存地址:\t\t" << &d << endl; cout << "虚函数表地址:\t\t\t" << ( int*)(&d) << endl; cout << "虚函数表 — 第1个函数地址:\t" << ( int*)*( int*)(&d) << "\t即析构函数地址" << endl; cout << "虚函数表 — 第2个函数地址:\t" << (( int*)*( int*)(&d) + 1) << "\t"; typedef void(*Fun)( void); Fun pFun = (Fun)*((( int*)*( int*)(&d)) + 1); pFun(); d.print(); cout << endl; cout << "虚函数表 — 第3个函数地址:\t" << *(( int*)*( int*)(&d) + 2) << "【结束】\t"; cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int*)(&d) + 1) << "\t通过地址取得的值:" << *(( int*)(&d) + 1) << endl; cout << "推测数据成员iDerived地址:\t" << (( int*)(&d) + 2) << "\t通过地址取得的值:" << *(( int*)(&d) + 2) << endl; } |
输出结果如下图所示:
C++对象模型中加入多继承
从单继承可以知道,派生类中只是扩充了基类的虚函数表。如果是多继承的话,又是如何扩充的?
1) 每个基类都有自己的虚表。
2) 子类的成员函数被放到了第一个基类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个基类的虚表中的print()函数都被overwrite成了子类的print ()。这样做就是为了解决不同的基类类型的指针指向同一个子类实例,而能够调用到实际的函数。
上面3个类,Derived_Mutlip_Inherit继承自Base、Base_1两个类,Derived_Mutlip_Inherit的结构如下所示:
为了验证上述C++对象模型,我们编写如下测试代码。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
void test_multip_inherit()
{ Derived_Mutlip_Inherit dmi( 3333); cout << "对象dmi的起始内存地址:\t\t" << &dmi << endl; cout << "虚函数表_vptr_Base地址:\t" << ( int *)(&dmi) << endl; cout << "_vptr_Base — 第1个函数地址:\t" << ( int *)*( int *)(&dmi) << "\t即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址:\t" << (( int *) * ( int *)(&dmi) + 1) << "\t"; typedef void(*Fun)( void); Fun pFun = (Fun) * ((( int *) * ( int *)(&dmi)) + 1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址:\t" << (( int *) * ( int *)(&dmi) + 2) << "\t"; pFun = (Fun) * ((( int *) * ( int *)(&dmi)) + 2); pFun(); cout << endl; cout << "_vptr_Base — 第4个函数地址:\t" << *(( int *) * ( int *)(&dmi) + 3) << "【结束】\t"; cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int *)(&dmi) + 1) << "\t通过地址取得的值:" << *(( int *)(&dmi) + 1) << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "虚函数表_vptr_Base1地址:\t" << (( int *)(&dmi) + 2) << endl; cout << "_vptr_Base1 — 第1个函数地址:\t" << ( int *)*(( int *)(&dmi) + 2) << "\t即析构函数地址" << endl; cout << "_vptr_Base1 — 第2个函数地址:\t" << (( int *) * (( int *)(&dmi) + 2) + 1) << "\t"; typedef void(*Fun)( void); pFun = (Fun) * (( int *) * (( int *)(&dmi) + 2) + 1); pFun(); cout << endl; cout << "_vptr_Base1 — 第3个函数地址:\t" << *(( int *) * ( int *)(( int *)(&dmi) + 2) + 2) << "【结束】\t"; cout << endl; cout << "推测数据成员iBase1地址:\t" << (( int *)(&dmi) + 3) << "\t通过地址取得的值:" << *(( int *)(&dmi) + 3) << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_GREEN); cout << "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++" << endl; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_INTENSITY | FOREGROUND_RED); cout << "推测数据成员iDerived地址:\t" << (( int *)(&dmi) + 4) << "\t通过地址取得的值:" << *(( int *)(&dmi) + 4) << endl; } |
输出结果如下图所示:
C++对象模型中加入虚继承
虚继承是为了解决重复继承中多个间接父类的问题的,所以不能使用上面简单的扩充并为每个虚基类提供一个虚函数指针(这样会导致重复继承的基类会有多个虚函数表)形式。
虚继承的派生类的内存结构,和普通继承完全不同。虚继承的子类,有单独的虚函数表,另外也单独保存一份父类的虚函数表,两部分之间用一个四个字节的0x00000000来作为分界。派生类的内存中,首先是自己的虚函数表,然后是派生类的数据成员,然后是0x0,之后就是基类的虚函数表,之后是基类的数据成员。
如果派生类没有自己的虚函数,那么派生类就不会有虚函数表,但是派生类数据和基类数据之间,还是需要0x0来间隔。
因此,在虚继承中,派生类和基类的数据,是完全间隔的,先存放派生类自己的虚函数表和数据,中间以0x分界,最后保存基类的虚函数和数据。如果派生类重载了父类的虚函数,那么则将派生类内存中基类虚函数表的相应函数替换。
简单虚继承(无重复继承情况)
简单虚继承的2个类Base、Derived_Virtual_Inherit1的关系如下所示:
Derived_Virtual_Inherit1的对象模型如下图: 下面看到的是has-a的关系
为了验证上述C++对象模型,我们编写如下测试代码。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void test_single_vitrual_inherit()
{ Derived_Virtual_Inherit1 dvi1( 88888); cout << "对象dvi1的起始内存地址:\t\t" << &dvi1 << endl; cout << "虚函数表_vptr_Derived..地址:\t\t" << ( int *)(&dvi1) << endl; cout << "_vptr_Derived — 第1个函数地址:\t" << ( int *)*( int *)(&dvi1) << endl; typedef void(*Fun)( void); Fun pFun = (Fun) * (( int *) * ( int *)(&dvi1)); pFun(); cout << endl; cout << "_vptr_Derived — 第2个函数地址:\t" << *(( int *) * ( int *)(&dvi1) + 1) << "【结束】\t"; cout << endl; cout << "=======================:\t" << (( int *)(&dvi1) + 1) << "\t通过地址取得的值:" << ( int *)*(( int *)(&dvi1) + 1) << "\t" << *( int *)*(( int *)(&dvi1) + 1) << endl; cout << "推测数据成员iDerived地址:\t" << (( int *)(&dvi1) + 2) << "\t通过地址取得的值:" << *(( int *)(&dvi1) + 2) << endl; cout << "=======================:\t" << (( int *)(&dvi1) + 3) << "\t通过地址取得的值:" << *(( int *)(&dvi1) + 3) << endl; cout << "虚函数表_vptr_Base地址:\t" << (( int *)(&dvi1) + 4) << endl; cout << "_vptr_Base — 第1个函数地址:\t" << ( int *)*(( int *)(&dvi1) + 4) << "\t即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址:\t" << (( int *) * (( int *)(&dvi1) + 4) + 1) << "\t"; pFun = (Fun) * (( int *) * (( int *)(&dvi1) + 4) + 1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址:\t" << (( int *) * (( int *)(&dvi1) + 4) + 2) << "【结束】\t" << *(( int *) * (( int *)(&dvi1) + 4) + 2); cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int *)(&dvi1) + 5) << "\t通过地址取得的值:" << *(( int *)(&dvi1) + 5) << endl; } |
输出结果如下图所示:
菱形继承(含重复继承、多继承情况)
菱形继承关系如下图:
Derived_Virtual的对象模型如下图:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
void test_multip_vitrual_inherit()
{ Derived_Virtual dvi1( 88888); cout << "对象dvi1的起始内存地址:\t\t" << &dvi1 << endl; cout << "虚函数表_vptr_inherit1地址:\t\t" << ( int*)(&dvi1) << endl; cout << "_vptr_inherit1 — 第1个函数地址:\t" << ( int*)*( int*)(&dvi1) << endl; typedef void(*Fun)( void); Fun pFun = (Fun)*(( int*)*( int*)(&dvi1)); pFun(); cout << endl; cout << "_vptr_inherit1 — 第2个函数地址:\t" << (( int*)*( int*)(&dvi1) + 1) << endl; pFun = (Fun)*(( int*)*( int*)(&dvi1) + 1); pFun(); cout << endl; cout << "_vptr_inherit1 — 第3个函数地址:\t" << (( int*)*( int*)(&dvi1) + 2) << "\t通过地址取得的值:" << *(( int*)*( int*)(&dvi1) + 2) << "【结束】\t"; cout << endl; cout << "======指向=============:\t" << (( int*)(&dvi1) + 1) << "\t通过地址取得的值:" << ( int*)*(( int*)(&dvi1) + 1)<< "\t" <<*( int*)*(( int*)(&dvi1) + 1) << endl; cout << "推测数据成员iInherit1地址:\t" << (( int*)(&dvi1) + 2) << "\t通过地址取得的值:" << *(( int*)(&dvi1) + 2) << endl; // cout << "虚函数表_vptr_inherit2地址:\t" << (( int*)(&dvi1) + 3) << endl; cout << "_vptr_inherit2 — 第1个函数地址:\t" << ( int*)*(( int*)(&dvi1) + 3) << endl; pFun = (Fun)*(( int*)*(( int*)(&dvi1) + 3)); pFun(); cout << endl; cout << "_vptr_inherit2 — 第2个函数地址:\t" << ( int*)*(( int*)(&dvi1) + 3) + 1 << "\t通过地址取得的值:" << *(( int*)*(( int*)(&dvi1) + 3) + 1) << "【结束】\t" << endl; cout << endl; cout << "======指向=============:\t" << (( int*)(&dvi1) + 4) << "\t通过地址取得的值:" << ( int*)*(( int*)(&dvi1) + 4) << "\t" <<*( int*)*(( int*)(&dvi1) + 4)<< endl; cout << "推测数据成员iInherit2地址:\t" << (( int*)(&dvi1) + 5) << "\t通过地址取得的值:" << *(( int*)(&dvi1) + 5) << endl; cout << "推测数据成员iDerived地址:\t" << (( int*)(&dvi1) + 6) << "\t通过地址取得的值:" << *(( int*)(&dvi1) + 6) << endl; cout << "=======================:\t" << (( int*)(&dvi1) + 7) << "\t通过地址取得的值:" << *(( int*)(&dvi1) + 7) << endl; // cout << "虚函数表_vptr_Base地址:\t" << (( int*)(&dvi1) + 8) << endl; cout << "_vptr_Base — 第1个函数地址:\t" << ( int*)*(( int*)(&dvi1) + 8) << "\t即析构函数地址" << endl; cout << "_vptr_Base — 第2个函数地址:\t" << (( int*)*(( int*)(&dvi1) + 8) + 1) << "\t"; pFun = (Fun)*(( int*)*(( int*)(&dvi1) + 8) + 1); pFun(); cout << endl; cout << "_vptr_Base — 第3个函数地址:\t" << (( int*)*(( int*)(&dvi1) + 8) + 2) << "【结束】\t" << *(( int*)*(( int*)(&dvi1) + 8) + 2); cout << endl; cout << "推测数据成员iBase地址:\t\t" << (( int*)(&dvi1) + 9) << "\t通过地址取得的值:" << *(( int*)(&dvi1) + 9) << endl; } |