C++对象模型3--无重写的单继承
C++对象模型中加入单继承
不管是单继承、多继承,还是虚继承,如果基于“简单对象模型”,每一个基类都可以被派生类中的一个slot指出,该slot内包含基类对象的地址。这个机制的主要缺点是,因为间接性而导致空间和存取时间上的额外负担;优点则是派生类对象的大小不会因其基类的改变而受影响。
如果基于“表格驱动模型”,派生类中有一个slot指向基类表,表格中的每一个slot含一个相关的基类地址(这个很像虚函数表,内含每一个虚函数的地址)。这样每个派生类对象汗一个bptr,它会被初始化,指向其基类表。这种策略的主要缺点是由于间接性而导致的空间和存取时间上的额外负担;优点则是在每一个派生类对象中对继承都有一致的表现方式,每一个派生类对象都应该在某个固定位置上放置一个基类表指针,与基类的大小或数量无关。第二个优点是,不需要改变派生类对象本身,就可以放大,缩小、或更改基类表。
不管上述哪一种机制,“间接性”的级数都将因为集成的深度而增加。C++实际模型是,对于一般继承是扩充已有存在的虚函数表;对于虚继承添加一个虚函数表指针。
无重写的单继承
无重写,即派生类中没有于基类同名的虚函数。
基类和派生类的代码:
//Base.h #pragma once #include<iostream> using namespace std; class Base { public: Base(int); virtual ~Base(void); virtual void print(void) const; protected: int iBase; };
//Base.cpp #include "Base.h" Base::Base(int i) { iBase = 1; cout << "Base_1::Base()" << endl; } Base::~Base(void) { cout << "Base::~Base()" << endl; } void Base::print(void) const { cout<<"Base_1::print(), iBase " << iBase << endl; }
//Derived.h #pragma once #include "base.h" class Derived : public Base { public: Derived(int); virtual ~Derived(void); virtual void derived_print(void); protected: int iDerived; };
//Derived.cpp #include "Derived.h" 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++对象模型,我们编写如下测试代码。
void test_single_inherit_norewrite() { Derived d(9999); cout << "对象d的起始内存地址:" << &d << endl; //获取类型信息 cout << "type_info信息的地址:" << ((int*)*(int*)(&d) - 1) << endl; //cout得到一个地址,就输出这个地址里存放的内容 RTTICompleteObjectLocator str = *((RTTICompleteObjectLocator*)*((int*)*(int*)(&d) - 1)); string classname(str.pTypeDescriptor->name); cout << classname << endl; //获取虚函数信息 cout << "虚函数表地址:" << (int*)(&d) << endl; cout << "虚函数表中第1个函数占位符的地址:" << (int*)*(int*)(&d) << "即析构函数在虚函数表中占位符的地址" << endl; cout << "虚函数表中第2个函数占位符的地址:" << ((int*)*(int*)(&d) + 1) << endl; typedef void(*Fun)(void); Fun pFun = (Fun)*((int*)*(int*)(&d) + 1); pFun(); d.print(); cout << endl; cout << "虚函数表中第3个函数占位符的地址:" << ((int*)*(int*)(&d) + 2) << endl; pFun = (Fun)*((int*)*(int*)(&d) + 2); pFun(); d.derived_print(); cout << endl; //获取成员变量的信息 cout << "推测成员变量iBase的地址:" << (int*)(&d) + 1 << endl; cout << "通过地址取得的iBase的值:" << *((int*)(&d) + 1) << endl; cout << "推测成员变量iDerived地址:" << (int*)(&d) + 2 << endl; cout << "通过地址取得的iDerived的值:" << *((int*)(&d) + 2) << endl; }
为了支持RTTICompleteObjectLocator必须引入一个头文件,这个头文件中定义了一些结构体,这些结构体封装了类的相关信息。
//type_info.h #pragma once typedef unsigned long DWORD; struct TypeDescriptor { DWORD ptrToVTable; DWORD spare; char name[8]; }; struct PMD { int mdisp; //member displacement int pdisp; //vbtable displacement int vdisp; //displacement inside vbtable }; struct RTTIBaseClassDescriptor { struct TypeDescriptor* pTypeDescriptor; //type descriptor of the class DWORD numContainedBases; //number of nested classes following in the Base Class Array struct PMD where; //pointer-to-member displacement info DWORD attributes; //flags, usually 0 }; struct RTTIClassHierarchyDescriptor { DWORD signature; //always zero? DWORD attributes; //bit 0 set = multiple inheritance, bit 1 set = virtual inheritance DWORD numBaseClasses; //number of classes in pBaseClassArray struct RTTIBaseClassArray* pBaseClassArray; }; struct RTTICompleteObjectLocator { DWORD signature; //always zero ? DWORD offset; //offset of this vtable in the complete class DWORD cdOffset; //constructor displacement offset struct TypeDescriptor* pTypeDescriptor; //TypeDescriptor of the complete class struct RTTIClassHierarchyDescriptor* pClassDescriptor; //describes inheritance hierarchy };
测试结果:
注意:有一个点需要说明一下,从代码和执行的结果中可以看出通过函数指针调用的函数和通过对象调用的函数,打印的成员变量的值是不一样的,具体的原因是C++在调用成员函数的时候,会把某一个对象传递给一个成员函数隐藏的函数参数this指针。这样,这个成员函数就知道了去操作哪一个对象的数据,但是通过函数指针调用成员函数的话,就没有一个对象来初始化这个成员函数中的this指针。所以通过函数指针调用成员函数是找不到具体的操作对象的,所以打印的值是一个随机值。