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

6
关于这篇文章: 对于使用虚函数表的实现,答案是: 是的,通常需要。你可能认为抽象类不需要虚函数表,因为派生类将有自己的虚函数表,但实际上在构建过程中仍然需要虚函数表。因为当基类被构造时,它会将虚函数表指针设置为自己的虚函数表。稍后,当进入派生类构造函数时,它将使用自己的虚函数表。我认为答案是正确的,但我不太明白为什么构造函数确切地需要虚函数表。

1
请参见http://stackoverflow.com/questions/18580779/virtual-function-calls-in-constructor-and-destructor。 - Oliver Charlesworth
1
"通常是的。",在引用的文本之后,他展示了一个指令让视觉去除它。 - Jarod42
2
这样,如果构造函数调用虚方法,它将调用自己的实现,而不是派生类的实现。 - user207421
3个回答

3
因为标准如此。

[class.cdtor]/4

当从构造函数或析构函数中直接或间接调用虚函数,包括在类的非静态数据成员构造或销毁期间,并且调用所适用的对象是正在构建或销毁的对象(称其为x),则被调用的函数是构造函数或析构函数类中的最终覆盖函数,而不是更派生类中覆盖它的函数。
这样处理的原因是,首先构造基类,然后构造派生类。如果在基类的构造函数内部调用虚函数,则调用派生类将会出现问题,因为派生类尚未初始化。
请记住,抽象类可能具有非纯虚函数。此外,出于调试目的,将纯虚函数指向调试陷阱(例如MSVC调用 _purecall())是很好的做法。
如果所有虚函数都是纯虚函数,在MSVC中,您可以使用 __declspec(novtable) 省略vtable。如果您使用了大量接口类,这可能会导致显著的节省,因为您省略了vfptr初始化。但是,如果您意外调用了纯虚函数,您将遇到难以调试的访问冲突问题。

0

vtables是C++中的实现问题,它们不是标准的一部分。

vtables用于方法的动态调度和RTTI。虽然在纯抽象类中,nullptr vtable指针可以用于动态调度(因为只有在具有该类型实例时才使用vtable指针),但对纯抽象类进行dynamic_cast是合法的,这可能需要vtable本身存在。

C++实现和ABI的设计者可能只是给纯抽象类(一个没有实现方法,只有=0的类)一个vtable,以使他们的实现更简单。每个类都有一个vtable,并且vtable指针在该类的构造过程中设置。代码可以依赖于vtable指针存在的事实,而不必每次都检查null。代码不必问诸如“这是一个纯抽象类吗”的问题。

对于非纯虚抽象类(其中一些方法具有实现,但某些方法是纯虚拟的),在构造/析构期间,您可以定义(如果出乎意料)涉及调用恰好是该类版本的给定方法,而不是基类方法或继承方法的行为。为此,您需要设置vtable。对于纯虚抽象类,不存在这样的调用的定义结果,因此vtable是多余的,但对于不完全抽象的抽象类,情况并非如此。

在C++中,不存在所谓的“纯抽象”。 - curiousguy
2
@curiousguy 如果你无法实例化一个类(它至少有一个纯虚函数),那么这个类可以被称为抽象类。如果一个类仅包含纯虚函数,那么它可以被称为纯抽象类。我承认,这些都不是C++标准中的术语,但我认为它们的使用并不会引起混淆。我已经添加了澄清。 - Yakk - Adam Nevraumont
"dynamic_cast 到一个纯抽象类是合法的",你能提供这样一个转换的例子吗? - curiousguy
@curious 这段代码定义了三个类A、B和C。其中,A是一个纯虚类,拥有一个纯虚函数foo();B是一个虚类,拥有一个虚析构函数;C继承自B和A,并实现了foo()函数。接着,创建了一个C类的对象b,并将其转换为A类指针a,最后调用a的foo()函数。这段代码的作用是通过动态类型转换,将C类对象的指针转换为A类指针,并调用A类中的foo()函数。 - Yakk - Adam Nevraumont
相对复杂的 dynamic_cast 代码将使用基类 B 的 vptr 类型信息来查找最派生类 C,然后遍历继承树以查找基类 A;连同继承树一起,运行时类型信息包含基类子对象的相对偏移量。 - curiousguy

-1

当你的类有一个纯虚函数时,这并不意味着你不能为它编写实现代码。因此,你可以拥有一个抽象类,它也是完全实现的。你的抽象类的构造函数必须能够调用所有函数,即使是到目前为止存在的纯虚函数,因为这一点。

如果你替换了客户端,那么基类构造函数的行为将取决于派生类,这不是一个好主意,所以不允许这样做。你可以不使用虚表,并静态解析所有函数调用,这样可以工作,但它意味着与所有其他函数相比,需要特别处理构造函数,并且需要内联所有其他函数来完成这个过程(因为从构造函数调用的函数可能还会调用虚函数等)- 这不是非常实用。

因此,它只是为构造函数和析构函数实现了一个虚表,在构造和销毁期间使用。它允许你在构造函数和析构函数中使用typeid和dynamic_cast,并获得可预测的结果,并从你拥有的虚函数中获得可靠的行为。没有其他替代方案可以做到这一点。


我认为这根本不意味着那样。纯虚函数都可以有定义,但这并不意味着抽象类已经完全实现了。从实际意义上讲,它肯定没有,因为它永远无法被实例化,以便可以调用成员函数。具体的派生类只需负责维护虚表。真正的答案是因为标准规定了它的工作方式。 - Cody Gray
即使为纯虚函数提供了定义,也不能使用虚函数的方式调用纯虚函数! - curiousguy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接