一、虚继承
1.1 为什么要有虚继承
先看一段代码:
d类继承b类和c类,但是b类和c类又单独继承一个a类导致d中存在了两份基类a ,导致编译器无法找到正确的基类。
为了解决这种情况,设计者就设计了虚继承来解决此类情况。
2.2 虚继承的声明
这样声明的话,我们d类就可以拥有一份基类了。
二、虚表和虚基表
2.1 虚表
如果类中声明的方法是用virtual修饰的,那么这个方法(函数)就是虚函数,而虚函数的在内存中的存储方法与普通函数是截然不同的。
当虚函数声明时,编译器会创建一个虚函数表(本质是一个函数指针数组),将当前声明的虚函数依次放入虚函数表中,然后将当前的虚函数表地址放入对象模型的最起始位置。
当我们调试时,可以看到会多出一个_vfptr,这个就是虚表指针
当我们在基类中在添加一个虚函数 e()时可以看到,_vfptr指针数组中会多一个指针成员。
总的来说,虚表就是来存储虚函数指针的,并且虚表是存放在创建对象内存中的(比如我创建一个b类,那么b的虚表就存在b对象内存中)。
2.2 虚基表
#include<iostream>
using namespace std;
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{
int d;
};
int main() {
D* d = new D();
return 0;
}
在这段代码中,虽然B类和C类都虚继承了A类,但是在D类中继承的B类和C类的内存空间中没有存储A类的对象,而是放在了D类的最后,并且除了D类新增加的变量或者函数外,继承的只有一个指针,这个指针就被称为虚基类指针
虚指针(vptr)
- 虚指针是一种特殊的指针类型,它在C++中用于实现多态性。通过虚指针,可以在运行时确定对象的实际类型,并调用适当的成员函数。
- 在C++中,如果一个类中至少有一个虚函数,那么编译器会为该类生成一个虚函数表(VTable),其中存储了该类所有虚函数的地址。
- 虚指针就是指向虚函数表的指针,在每个对象中都有一个虚指针。
- 当调用一个虚函数时,编译器会根据虚指针指向的虚函数表确定要调用哪个函数,然后进行调用。
虚指针作用
实现了动态绑定,即在运行时确定对象的实际类型。这样就可以实现基类指针指向派生类对象,并且能够正确调用派生类中重载的虚函数。
虚函数这么重要,因此我们可以通过它的作用知道,如果虚指针还未初始化,或者是虚表没有创建,那么调用虚函数是十分危险的(因为找不到!);那么虚函数是在哪里初始化呢?
答案是在构造函数中进行虚表的创建和虚表指针的初始化。
虚表
-
虚函数表是一个数组:虚函数表是一个由函数指针组成的数组,每个函数指针指向一个虚函数的地址。
-
顺序与声明顺序一致:虚函数表中的函数指针的顺序与类中声明虚函数的顺序一致。这样可以确保通过偏移量来访问正确的虚函数。
-
一个类对应一个虚函数表:每个类都有自己独立的虚函数表。如果一个类继承自另一个类,那么它会在父类的虚函数表的基础上扩展,新增的虚函数会添加到子类的虚函数表中。
-
类的对象共享虚函数表:同一类的所有对象共享同一个虚函数表,这样可以减少内存占用。
-
虚指针指向虚函数表:每个对象中都有一个隐藏的虚指针(vptr),它指向对象所属类的虚函数表的起始地址。通过虚指针,可以在运行时确定要调用的虚函数。