为什么抽象类有一个虚函数表?

Why does an abstract class have a vtable?

本文关键字:函数 抽象类 有一个 为什么      更新时间:2023-10-16

关于这篇文章:

对于使用vtable的实现,答案是:通常是。你可能会认为抽象类不需要虚表,因为派生类将有自己的虚函数表,但在期间需要它。构造:在构造基类时,它设置虚表指针指向自己的虚表。之后当派生类如果输入构造函数,它将使用自己的虚函数表。

我假设答案是正确的,但我不太明白。为什么构造需要虚表?

因为标准是这么说的。

[class.cdtor]/4

对象直接或间接调用虚函数时构造函数或析构函数,包括在构造或期间类的非静态数据成员的销毁,以及对象的调用应用的对象是正在构造的对象(称之为x)还是类中调用的函数是最后的重写构造函数或析构函数的类,而不是在派生类。

基本原理是首先构造基类,然后派生类。如果在基类的构造函数内部调用虚函数,那么调用派生类是不好的,因为派生类还没有初始化。

记住抽象类可以有非纯虚函数。此外,出于调试目的,最好将纯虚函数指向调试陷阱(例如,MSVC调用_purecall())。

如果所有虚函数都是纯虚函数,那么在MSVC中可以用__declspec(novtable)省略虚函数表。如果您使用大量的接口类,这可以节省大量的开销,因为您省略了vfptr初始化。但是,如果不小心调用了纯虚函数,就会遇到难以调试的访问冲突。

变量表是c++中的实现问题,它们不是标准的一部分。

虚表既用于方法的动态调度,也用于RTTI。在纯抽象类中,nullptr虚表指针可以用于动态调度(因为虚表指针仅在具有该类型的实例时使用),而dynamic_cast指向纯抽象类是合法的,并且可能需要虚表本身存在。

c++实现和ABI的设计者可以简单地给纯抽象类(没有实现方法的类,只有=0方法)一个变量表,以使他们的实现更简单。每个类都有一个虚值表,虚值表指针在该类构造期间被设置。这样,代码就可以依赖虚函数表指针存在的事实,而不必每次都检查是否为空。代码不需要问"这是一个纯抽象类吗?"。

对于非纯抽象类(其中一些方法具有实现,但有些方法是纯虚的),在构造/销毁期间,您可以定义(如果意外)行为,该行为涉及调用该类的给定方法版本,而不是基类方法或继承方法。为此,您需要设置一个虚表。对于一个纯抽象类,这样的调用没有定义的结果,所以虚函数表是多余的,但是对于一个不是完全抽象的抽象类,这就不成立了。

当你的类有一个纯虚函数时,这并不意味着你不能有它的实现(!!)。这意味着你可以有一个抽象类,它也是完全实现的。抽象类的构造函数必须能够调用迄今为止为其存在的所有函数——甚至是纯虚函数。

如果你替换了客户端,你会得到基类构造函数的不同行为,这取决于派生类-不是一个好主意,所以这是不允许的。你可以不使用虚函数表,并静态解析所有函数调用——这是可行的,但这意味着要处理构造函数,而不是处理所有其他函数,并且需要内联所有其他函数来做到这一点(因为从构造函数调用的函数也可能调用虚函数等)——不太实用。

所以它只是为构造函数和析构函数实现了一个虚参表,以便在构造和析构过程中使用。它允许您在c'tor和d'tor中使用typeid和dynamic_cast来获得可预测的结果,并从您拥有的虚函数中获得可靠的行为。没有其他解决方案可以做到这一点。