继承这一块被虚表和虚基表弄的特别晕,专开一篇来研究研究:
首先:虚表和虚基表无关系!!!
虚函数:虚函数是在类的非静态成员函数前加virtual,则这个成员函数成为虚函数(并不是所有的成员函数能够定义为虚函数,如构造函数等),在某基类中声明为 virtual 并在一个或多个派生类中被重新定义的成员函数,实现多态性。
虚表(虚函数表):C++中的虚函数的实现一般是通过虚函数表(Virtual Table)来实现的。简称为V-Table。 这张表解决了继承、覆盖的问题,指明了实际所应该调用的函数。虚表可以看做一个指针数组,存放的是所有虚函数的指针,它以NULL来作为虚表的结束标志。
虚继承:C++使用虚拟继承(Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。
虚基表:解决菱形继承产生的二义性的问题,在虚基表中存放的到虚基类的偏移量,虚基表也是以NULL结尾的。
首先,实现一个打印虚表的函数:
typedef void(*FUNC)(); void PrintVTable(int * VTable) { cout << "虚表地址" << VTable << endl; int i = 0; for (i = 0; VTable[i] != NULL; i++) { printf("第[%d]个虚函数地址为 : 0x%p, -> ", i, VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout << endl; }
但是该函数只能适用于32位机器的虚表打印,如果需要既能在32位又能在64位机器下打印的功能:需要将打印函数int*修改int**,并且调用时也应将int*修改为 : int**
typedef void(*FUNC)(); void PrintVTable(int** VTable) { cout << "虚表地址" << VTable << endl; int i = 0; for (i = 0; VTable[i] != NULL; i++) { printf("第[%d]个虚函数地址为 : 0x%p, -> ", i, VTable[i]); FUNC f = (FUNC)VTable[i]; f(); } cout << endl; }
由于我的机器是32位,这里这里将使用第一个函数打印虚表。
虚表的内存对象模型:
带虚表的单继承模型:
class A { public: virtual void f1() { cout << "A::f1()" << endl; } virtual void f2() { cout << "A::f2()" << endl; } int _a; }; class B : public A { public: virtual void f1() { cout << "B::f1()" << endl; } virtual void f3() { cout << "B::f3()" << endl; } int _b; }; int main() { A a; a._a = 1; PrintVTable((int*)(*(int*)&a)); B b; b._a = 3; b._b = 2; PrintVTable((int*)(*(int*)&b)); system("pause"); return 0; }
由这张图可以看出来:
1.子类和父类具有不同的虚表,且子类将父类的虚表复制了下来,只是修改了自己重写的部分。
2.虚表以NULL结尾。
要打印虚表就要先把虚标的地址取出来:(int*)(*(int*)&b)
因此就可以画出来带虚表的单继承模型:
带虚表的多继承模型:
class A { public: virtual void f1() { cout << "A::f1()" << endl; } virtual void f2() { cout << "A::f2()" << endl; } int _a; }; class B { public: virtual void f1() { cout << "B::f1()" << endl; } virtual void f2() { cout << "B::f2()" << endl; } int _b; }; class C : public A, public B { public: virtual void f1() { cout << "C::f1()" << endl; } virtual void f3() { cout << "C::f3()" << endl; } int _c; }; int main() { C c; c._a = 1; c._b = 2; c._c = 3; PrintVTable((int*)(*(int*)&c)); PrintVTable((int*)(*(int*)((char*)&c+ sizeof(A)))); system("pause"); return 0; }
由这幅图可以看出来: 子类的虚函数添加到了先继承的类的虚表中。
对虚标进行打印:
由此得出多继承的对象模型:
class A{ public: virtual void fun1() { cout << "v-A::fun1()" << endl; } virtual void fun2() { cout << "v-A::fun2()" << endl; } int _a; }; class B : virtual public A { public: virtual void fun1() { cout << "v-B::fun1()" << endl; } virtual void fun3() { cout << "v-B::fun3()" << endl; } int _b; }; class C : virtual public A { public: virtual void fun1() { cout << "v-C::fun1()" << endl; } virtual void fun4() { cout << "v-C::fun4()" << endl; } int _c; }; class D : public B, public C { public: virtual void fun1() { cout << "v-C::fun1()" << endl; } virtual void fun5() { cout << "v-C::fun5()" << endl; } virtual void fun6() { cout << "v-C::fun6()" << endl; } int _d; }; int main() { D d; d.B::_a = 1; d._a = 2; d.C::_a = 3; d._b = 4; d._c = 5; d._d = 6; system("pause"); return 0; }
存在菱形继承时虚继承的对象模型:
菱形继承的虚基表(偏移量表):
虚表和虚基表的对象模型图示:
打印菱形继承的虚表要注意不能直接加上sizeof(B),因为B中还包含A.
应使用下面的方式打印:
PrintVTable((int*)(*(int*)((char*)&d + sizeof(B) - sizeof(A)))); PrintVTable((int*)(*(int*)((char*)&d + sizeof(D) - sizeof(A))));