探索对象模型
先看一个例子:
class Base { public: virtual void func_1() {} virtual void func_2() {} protected: int _a; }; int main() { Base b1; Base b2; system("pause"); return 0; }
_vfptr是虚表指针,指向这个类的虚表,事实上_vfptr是一个指针数组,里面放的都是虚函数的地址,并且这个数组以NULL结束。而且同一类对象公用一张虚表。
二、单继承对象模型
class Base { public: virtual void func_1()//虚函数 { cout << "Base ::func_1()\n"; } virtual void func_2()//虚函数 { cout << "Base ::func_2()\n"; } protected: int _a; }; class Derive :public Base { public: virtual void func_1()//重写了父类中的虚函数 { cout << "Derive ::func_1()\n"; } virtual void func_3()//Derive 类自己的虚函数 { cout << "Derive ::func_3()\n"; } void func_4()//普通成员函数 { cout << "Derive ::func_4()\n"; } protected: int _b; }; typedef void (*VFUNC)(void);//声明一个函数指针,注意尽量将虚函数的返回值和参数都定义为一样的 //打印虚表内容,想要查看虚表的内容 void PrintVTable(int *table) { int i = 0; printf("table :0x%p\n", table);//打印虚表指针 for (i = 0; table[i] != NULL; ++i) { printf("table[%d]:0x%p->", i,table[i]);//将虚表中的每一个虚函数地址打印出来 VFUNC f = (VFUNC)table[i];//将虚函数的地址转换为函数指针 f();//调用虚函数 } } int main() { Base b1; Derive d1; PrintVTable((int *)(*((int *)(&b1)))); PrintVTable((int *)(*((int *)(&d1)))); system("pause"); return 0; }
查看结果:
我们看到父类的虚表和子类的虚表并不是一张虚表,并且内容也不同了,子类的虚表是如何构成的,我们可以这样理解,子类独自开辟一段空间,先将父类的虚表内容相当于是复制下来,如果自己有将父类中虚函数进行了重写的,就将其覆盖为自己的,再将自己独有的虚函数放进去。
并且一个对象被创建时在初始化列表中将虚表指针进行了初始化。
重写也叫覆盖,这里就很好理解了,重写可以说是从类的角度看,子类将父类中的虚函数进行了重写,覆盖就可以从对象模型角度来看,子类虚表中对父类的虚函数进行了覆盖。
三、值得一看
1.虚表不安全的一点
虚表中存放了该类了所有虚函数,不论是私有的还是保护的,例如下面的例子
class Base { public: virtual void func_1()//虚函数 { cout << "Base ::func_1()\n"; } virtual void func_2()//虚函数 { cout << "Base ::func_2()\n"; } protected: int _a; virtual void func_protect()//虚函数 { cout << "Base ::func_protect()\n"; } private: virtual void func_private()//虚函数 { cout << "Base ::func_private()\n"; } }; class Derive :public Base { public: virtual void func_1()//重写了父类中的虚函数 { cout << "Derive ::func_1()\n"; } virtual void func_3()//Derive 类自己的虚函数 { cout << "Derive ::func_3()\n"; } void func_4()//普通成员函数 { cout << "Derive ::func_4()\n"; } protected: int _b; }; typedef void (*VFUNC)(void);//声明一个函数指针,注意尽量将虚函数的返回值和参数都定义为一样的 //打印虚表内容,想要查看虚表的内容 void PrintVTable(int *table) { int i = 0; printf("table :0x%p\n", table);//打印虚表指针 for (i = 0; table[i] != NULL; ++i) { printf("table[%d]:0x%p->", i,table[i]);//将虚表中的每一个虚函数地址打印出来 VFUNC f = (VFUNC)table[i];//将虚函数的地址转换为函数指针 f();//调用虚函数 } } int main() { Base b1; Derive d1; PrintVTable((int *)(*((int *)(&b1)))); PrintVTable((int *)(*((int *)(&d1)))); system("pause"); return 0; }
结果:
我们发现两个问题:
(1)我们在类外面可以访问到类中的私有成员
(2)父类中的私有成员在子类中应该是不可见的,但是这里却将私有成员暴露出来
2.打印虚表函数
上面的函数传参是这样理解的
这里是在32位2平台下,一个指针的大小是4个字节,如果是在64位平台下,一个指针的大小为8个字节,我们就可以用
long long 来代替int,但是为了更好的适应不同的平台下指针的大小,我们可以用下列的形式,每次去取出的都是对象开始的
sizeof(int *)【一个指针大小】个字节。
PrintVTable( (int **) ( *( (int **) (&b1) ) ) ); void PrintVTable(int **table);本篇完,下一篇,探索多继承体系下的对象模型