一、多态的构成原理
虚函数表:与虚表(虚函数表)有关,只要有虚函数就有虚表,虚表内放了虚函数的地址。
虚函数表是通过一块连续的内存来存储虚函数的地址。这张表解决了继承,虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数。
下面我们调出监视窗口来看一个示例中的虚表:
静态(编译时)联编:
动态(运行时)联编:不构成多态,编译时以确定调用函数的地址。
构成多态,运行时在虚表里确定应调用函数的地址
二、多态的各种对象模型
首先写一个打印虚表的函数
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的对象模型
下面运行打印虚表函数来验证:
和预想完全相同!
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的虚表里。
下面来验证:
与预想相同!