福州营销型网站建设,东营+网站建设,网站设计与制作教程,东莞粤保保安公司虚表和虚基表 虚表虚基表虚拟继承和虚函数都存在时的对象模型 虚表
我们知道#xff0c;如果类中声明了的方法是用virtual进行修饰的#xff0c;则说明当前这个方法要作为虚函数#xff0c;而虚函数的存储和普通函数的存储是有区别的 当有虚函数声明时#xff0c;编译器会… 虚表和虚基表 虚表虚基表虚拟继承和虚函数都存在时的对象模型 虚表
我们知道如果类中声明了的方法是用virtual进行修饰的则说明当前这个方法要作为虚函数而虚函数的存储和普通函数的存储是有区别的 当有虚函数声明时编译器会创建一个虚函数表将当前的虚函数按照声明次序放入虚函数表中而这个虚函数表实际上就是一个函数指针数组然后将当前这个虚函数表的地址放入对象模型的最起始位置。
class A
{
public:virtual void fun1(){cout A::fun1() endl;}virtual void fun2(){cout A::fun2() endl;}virtual void fun3(){cout A::fun3() endl;}int _a;
};它对应的对象模型是这样的
所以说本质上虚函数表是一个函数指针数组而对象模型中存放的是虚函数表的首地址当我们需要调用虚函数时传递对应的对象就可以通过对象的地址获取对象的虚表指针从而获取虚表进而得到对应虚函数表中某个虚函数的地址以此来进行调用知道函数的入口地址就可以调用对应的函数
虚基表
我们知道当出现菱形继承时一定会出现对象模型中有多个基类对象成员。
//普通继承
class A
{
public:int _a;
};
class B : public A
{
public:int _b;
};
class C : public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
上述代码中D类对象中一定会存在B类和C类对象继承自A类对象的_a这个成员这样就出现了两份_a成员导致访问_a时出现二义性并且随着继承深度和广度的增加对象成员会越来越冗余。 为了解决这个问题出现了虚拟继承。
//菱形虚拟继承
class A
{
public:int _a;
};
class B : virtual public A
{
public:int _b;
};
class C : virtual public A
{
public:int _c;
};
class D : public B, public C
{
public:int _d;
};
通过让B类和C类虚拟继承A类后对象模型就从图1变成了图2 这样的转变使得B类和C类虽然继承了A类但是B类和C类中并没有存储A类的对象基类对象只有一份被存放在了整个对象模型的最后除了子类新增之外只有一个指针这个指针就被称为虚基表指针。 虚基表指针所指向的是一个虚基表对于B类ptr1这个虚基表总大小为8个字节32位系统下前4个字节存储的是子类对象相对于自己起始位置的偏移量目前来看是0当存在虚函数的虚拟继承时就不是0了后4个字节存储是子类对象相对于基类部分的偏移量。 ptr2指向C类这个对象的虚基表总大小为8个字节32位系统下前4个字节存储的是子类对象相对于自己起始位置的偏移量目前来看是0当存在虚函数的虚拟继承时就不是0了后4个字节存储是子类对象相对于基类部分的偏移量。 可以发现虚表在整个类对象中只存储一份也就是说一个类的不同对象共享同一份虚表。而虚基表有多份取决于当前类是否虚拟继承了基类若虚拟继承了基类就会创建一个虚基表指针指向一个虚基表。
虚拟继承和虚函数都存在时的对象模型
那么就存在另外一个问题当虚拟继承和虚函数同时出现在继承体系中对象模型又是什么样子呢
class A
{
public:virtual void fun1(){cout A::fun1() endl;}virtual void fun2(){cout A::fun2() endl;}int _a;
}
class B : virtual public A
{
public:virtual void fun1(){cout B::fun1() endl;}virtual void fun3(){cout B::fun3() endl;}int _b;
};
class C : virtual public A
{
public:virtual void fun2(){cout C::fun2() endl;}virtual void fun4(){cout C::fun4() endl;}int _c;
};
class D : public B, public C
{
public:virtual void fun1(){cout D::fun1() endl;}virtual void fun2(){cout D::fun2() endl;}virtual void fun5(){cout D::fun5() endl;}int _d;
}上述代码中B类和C类都继承自A类并且对A类中的虚函数进行了重写同时也新增了虚函数。D类继承了B和C类对B和C类中的虚函数进行了重写同时也新增了虚函数。 那么当前在这个继承体系下对象模型是什么样子呢 其实不难想到由于B类和C类都是虚拟继承那么A类成员只会保留一份在最下方同时B类和C类都会保存自己的虚基表指针而D类由于是普通继承按照顺序新增的虚函数被放到B类的虚表中。 我们通过取地址发现对象模型确实是上述的样子但是在D类和A类之间放了00000000作为对象分割区分猜测 请注意当前的验证情况是在vs2019中进行验证的。 总结当虚基表和虚表同时存在虚拟继承和虚函数同时存在时对象模型从整体上来说还是和虚拟继承相同基类对象顺序按照声明的顺序从上到下排列对象中没有祖父类的成员祖父类成员被放到了模型的最下方。但是由于有虚函数的存在B类对A类的虚函数进行重写的虚函数在A类中直接修改B类新增的虚函数被放到B类内部的虚表中C类对A类的虚函数进行重写的虚函数在A类中直接修改C类新增的虚函数被放到C类内部的虚表中。D类对B类和C类进行重写的虚函数直接在对应类中进行修改D类新增的就直接放到B类的虚表中。 通过上述的描述可以知道对于B类C类和A类的虚表中存放的虚函数分别为 而对于虚基表来说表示的是子类对象相对于自己起始位置的偏移量如果是B类B类对象的起始位置已经有了一个虚表指针那么虚基表中前四个字节要表示相对自己起始位置的偏移量就需要为-4而后四个字节是相对于基类的偏移量是正常的计算方式。 对于B类C类的虚基表来说其中的值为