多种情况下多态的对象模型

来源:互联网 发布:sql 注入 1 1 编辑:程序博客网 时间:2024/06/16 12:56

一、多态的构成原理

与虚表(虚函数表)有关,只要有虚函数就有虚表,虚表内放了虚函数的地址。
虚函数表:
虚函数表是通过一块连续的内存来存储虚函数的地址。这张表解决了继承,虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数。

下面我们调出监视窗口来看一个示例中的虚表:



静态(编译时)联编:
不构成多态,编译时以确定调用函数的地址。
动态(运行时)联编

构成多态,运行时在虚表里确定应调用函数的地址


二、多态的各种对象模型
首先写一个打印虚表的函数
typedef void(*V_FUNC) ();void PrintfVtable(int Vtable){int *Vf_array = (int*)Vtable;printf("vtable:0x%p\n", Vtable);for (size_t i = 0; Vf_array[i] != 0; ++i){printf("Vtable[%d]:0x%p ->", i, Vf_array[i]);V_FUNC f = (V_FUNC)Vf_array[i];f();}printf("---------------------------------\n");}

改进版本(32位、64位都可以运行)
typedef void(*V_FUNC) ();void PrintfVtable(int* Vtable)//传指针{printf("vtable:0x%p\n", Vtable);int ** PPVtable = (int**)Vtable;for (size_t i = 0; PPVtable[i] != 0; ++i){printf("Vtable[%d]:0x%p ->", i, PPVtable[i]);V_FUNC f = (V_FUNC)PPVtable[i];f();}}

1、多继承(有虚函数)
注意:多继承是子类虚表的内容放在第一个父类的虚表里
示例

typedef void(*V_FUNC) ();void PrintfVtable(int* Vtable)//传指针{printf("vtable:0x%p\n", Vtable);int ** PPVtable = (int**)Vtable;for (size_t i = 0; PPVtable[i] != 0; ++i){printf("Vtable[%d]:0x%p ->", i, PPVtable[i]);V_FUNC f = (V_FUNC)PPVtable[i];f();}}class Base1{public:virtual void func1(){cout << "Base1::func1" << endl;}virtual void func2(){cout << "Base1::func2" << endl;}private:int b1;};class Base2{public:virtual void func1(){cout << "Base2::func1" << endl;}virtual void func2(){cout << "Base2::func2" << endl;}private:int b2;};class Derive :public Base1, public Base2{public:virtual void func1(){cout << "Derive::func1" << endl;}virtual void func3()//注意放在哪?{cout << "Derive::func3" << endl;}private:int d1;};void test(){Derive d;//PrintfVtable(*(int**)&d);//Base1的虚表PrintfVtable(*((int**)((char*)&d+sizeof(Base1))));//Base2的虚表}int main(){test();return 0;}
下面分析d的对象模型

d继承了Base1和Base2的虚表,并且分别对Base1和Base2中的func1重写,自己的虚函数func3放在第一个父类base1 的虚表中。

下面运行打印虚表函数来验证:

和预想完全相同!

2、菱形继承(有虚函数)(复杂的多继承)
class A{public :virtual void func1(){cout << "A::func1()" << endl;}virtual void func2(){cout << "A::func2()" << endl;}int _a;};class B :public A{public:virtual void func1(){cout << "B::func1()" << endl;}virtual void func3(){cout << "B::func3()" << endl;}int _b;};class C :public A{public:virtual void func1(){cout << "C::func1()" << endl;}virtual void func4(){cout << "C::func4()" << endl;}int _c;};class D :public B, public C{public:virtual void func1(){cout << "D::func1()" << endl;}virtual void func5(){cout << "D::func5()" << endl;}int _d;};void test(){D d;d.B::_a = 1;d._b = 2;d.C::_a = 3;d._c = 4;d._d = 5;PrintfVtable(*(int**)&d);//打印B的虚表PrintfVtable(*((int**)((char*)&d+sizeof(B))));//打印C的虚表}int main(){test();return 0;}


下面我们来分析


打开内存窗口看对象d的地址

            发现有两个指针,这两个指针分别是D从B继承的虚表指针(00 2d dd 2c),和D从C继承的虚表指针(00 2d dd 40)。
下面我们来看分析这两个虚表指针指向的内容

运行打印虚表函数后验证
和预想相同!

·菱形虚拟继承
代码与菱形继承基本相似:
在B和C继承A时前加virtual关键字
class B :virtual public A
{}
class C : virtual public A
{}

下面分析对象模型


可见只有一个_a了解决了数据冗余的问题,除了三个虚表指针外,多了两个虚基表指针(01 06 dd f4 和 01 06 db b0)。
接下来看这两个虚基表指针的内容:

可见,B的虚基表指针存了个十进制的24,发现当B的虚基表指针本身的地址(0x0101FC18)+ 24字节= &A(0x0101FC30);C的虚基表指针存了个十进制的12,发现当C的虚基表指针本身的地址(0x0101FC24) + 12字节 = &A(0x0101FC30);

再来研究这三个虚表指针的内容:
先考虑为什么有3个虚表指针,为什么不像菱形继承一样有2个虚表指针?

本来B只继承A,B的虚表内容放在A的虚表内就可以,但是现在是菱形虚拟继承,B和C只有一个公共的A,若B的虚表内容放在A的虚表内,同理C也继承A,所以C的虚表内容也应放在A的虚表里,这样一来B和C的虚表内容都在A内,B和C互相可见,这样是不对的,B和C应该是相互独立的。所以现在有3个虚表指针,B和C的虚表指针分别放各自独有的虚表内容,公共的内容(从A继承的虚表)放在A的虚表里。

下面来验证:


与预想相同!

原创粉丝点击