C++重新认知:虚继承、虚指针以及虚表。

一、虚继承

1.1 为什么要有虚继承

先看一段代码:
C++重新认知:虚继承、虚指针以及虚表。_第1张图片
C++重新认知:虚继承、虚指针以及虚表。_第2张图片
d类继承b类和c类,但是b类和c类又单独继承一个a类导致d中存在了两份基类a ,导致编译器无法找到正确的基类。
为了解决这种情况,设计者就设计了虚继承来解决此类情况。

2.2 虚继承的声明

C++重新认知:虚继承、虚指针以及虚表。_第3张图片
C++重新认知:虚继承、虚指针以及虚表。_第4张图片

这样声明的话,我们d类就可以拥有一份基类了。

二、虚表和虚基表

2.1 虚表

如果类中声明的方法是用virtual修饰的,那么这个方法(函数)就是虚函数,而虚函数的在内存中的存储方法与普通函数是截然不同的。
当虚函数声明时,编译器会创建一个虚函数表(本质是一个函数指针数组),将当前声明的虚函数依次放入虚函数表中,然后将当前的虚函数表地址放入对象模型的最起始位置。
C++重新认知:虚继承、虚指针以及虚表。_第5张图片
C++重新认知:虚继承、虚指针以及虚表。_第6张图片

当我们调试时,可以看到会多出一个_vfptr,这个就是虚表指针
C++重新认知:虚继承、虚指针以及虚表。_第7张图片

当我们在基类中在添加一个虚函数 e()时可以看到,_vfptr指针数组中会多一个指针成员

总的来说,虚表就是来存储虚函数指针的,并且虚表是存放在创建对象内存中的(比如我创建一个b类,那么b的虚表就存在b对象内存中)。

2.2 虚基表

#include
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类新增加的变量或者函数外,继承的只有一个指针,这个指针就被称为虚基类指针
C++重新认知:虚继承、虚指针以及虚表。_第8张图片

虚指针(vptr)

  • 虚指针是一种特殊的指针类型,它在C++中用于实现多态性。通过虚指针,可以在运行时确定对象的实际类型,并调用适当的成员函数。
  • 在C++中,如果一个类中至少有一个虚函数,那么编译器会为该类生成一个虚函数表(VTable),其中存储了该类所有虚函数的地址。
  • 虚指针就是指向虚函数表的指针,在每个对象中都有一个虚指针。
  • 当调用一个虚函数时,编译器会根据虚指针指向的虚函数表确定要调用哪个函数,然后进行调用。

虚指针作用

实现了动态绑定,即在运行时确定对象的实际类型。这样就可以实现基类指针指向派生类对象并且能够正确调用派生类中重载的虚函数。

虚函数这么重要,因此我们可以通过它的作用知道,如果虚指针还未初始化,或者是虚表没有创建,那么调用虚函数是十分危险的(因为找不到!);那么虚函数是在哪里初始化呢?
答案是在构造函数中进行虚表的创建和虚表指针的初始化

虚表

  • 虚函数表是一个数组:虚函数表是一个由函数指针组成的数组,每个函数指针指向一个虚函数的地址。

  • 顺序与声明顺序一致:虚函数表中的函数指针的顺序与类中声明虚函数的顺序一致。这样可以确保通过偏移量来访问正确的虚函数。

  • 一个类对应一个虚函数表:每个类都有自己独立的虚函数表。如果一个类继承自另一个类,那么它会在父类的虚函数表的基础上扩展,新增的虚函数会添加到子类的虚函数表中。

  • 类的对象共享虚函数表:同一类的所有对象共享同一个虚函数表,这样可以减少内存占用。

  • 虚指针指向虚函数表:每个对象中都有一个隐藏的虚指针(vptr),它指向对象所属类的虚函数表的起始地址。通过虚指针,可以在运行时确定要调用的虚函数。

你可能感兴趣的:(C++重新认知系列,c++)