C++面试题:一个含有纯虚函数的类的vtable

14

我今天在一次非常尴尬的电话面试中被问到了这个问题:

一个拥有虚函数的类和一个拥有纯虚函数的类之间有何不同?

现在,我知道C++标准并没有具体规定vtable,甚至也没有规定v-table的存在...但从理论上讲,答案应该是什么呢?

我脱口而出,一个拥有纯虚函数的类可以有vtable,其vtable条目将指向派生类实现的纯虚函数。这个假设是否正确?然而,面试官并没有给我一个肯定的答案。

如果一个类只有纯虚函数,那么一个假想的编译器会创建一个vtable吗?如果这个类包含有定义的纯虚函数,情况会如何呢?(如:http://www.gotw.ca/gotw/031.htm


电话面试?你没有使用谷歌吗? - Mitch Wheat
4
如果我是一位经理,在电话面试中听到有打字声,我想我会立即淘汰这个人 :p - John Humphreys
我并没有说这是一个好主意,或者我同意这样做。面对现实吧,如果一个候选人不够聪明,无法掩盖他们正在使用谷歌的事实,你会雇用他们吗? - Mitch Wheat
掩盖自己的行为比使用谷歌更糟糕。我想知道有多少成功的工程师能在一周内不使用谷歌?一天呢?根据这个问题的答案去行动也不会让你成为一个好的工程师,相反地,会适得其反。 - Tom Kerr
我以前在电话面试中使用过谷歌,但最终总是感觉很不好。我宁愿说:“我不知道,我得查一下。” - gred
3个回答

24
对于非纯虚函数,在vtable中的每个条目将引用最终覆盖者或适应this指针的thunk。对于纯虚函数,vtable中的条目通常包含指向通用函数的指针,该函数会投诉并以一些明智的消息中止程序(例如“在此上下文中调用了纯虚函数”或类似的错误消息)。

如果一个类只有纯虚函数,那么会生成vtable吗?

是的,会生成,不同之处在于存储在表中的内容,而不是表的形状。在简单的方法中,纯虚函数为NULL指针,虚函数为非NULL指针。实际上,使用通常编译器的指向通用函数的指针,该函数会投诉并使用abort() 中止程序。

如果这个类包含有定义的纯虚函数呢?

这不会影响vtable。vtable仅用于动态分派,不会将调用动态分派到纯虚函数的定义(即您只能通过禁用动态分派来手动分派给纯虚函数,限定类型名称: x.base::f()将调用base::f,即使它是纯虚函数,但如果它是纯虚的,x.f()将永远不会分派到base::f


你只需要在多重继承时使用thunk,是吗?在单一继承中,基类和派生类从同一位置开始,因此无需调整“this”。 - Eran
@eran:没错,在线性继承层次结构中,所有具有虚函数的对象很可能具有相同的地址。 - Matthieu M.
@MatthieuM: "在线性继承层次结构中,所有具有虚函数的对象可能具有相同的地址":为什么? - user7
@MatthieuM:在标准中,简单的空指针是可以使用的,但我所知道的所有实现都添加了辅助函数。这是一种低成本高收益的方式来提高实现的质量,为什么只是触发崩溃,而不是提供有关出错信息并崩溃呢? - David Rodríguez - dribeas
@user7:只有在基类和派生类指针不同时才需要 thunk。在单继承关系中,base 子对象与 derived 子对象对齐,这意味着 (void*)static_cast<base*>(p) == (void*)p,其中 p 是指向派生类的指针(即 basederived 的内存位置相同)。当您拥有多重继承时,只有一个基类可以与 derived 对象对齐,因此对于多个基类中的其余部分,您需要更正 this 指针。 - David Rodríguez - dribeas
@DavidRodríguez-dribeas:我记得在发布模式下使用gcc 3.4(是的,它很老)会出现崩溃问题,也许我记错了,这是一段时间以前的事情了。 - Matthieu M.

1

在这种情况下,实现可以做任何事情,因为如果您的代码在需要动态解析的上下文中调用纯虚函数,并且它将解析为纯虚函数,则行为是未定义的。我见过几种不同的解决方案:编译器插入一个以错误消息终止的函数地址(从实现质量的角度来看,这是首选解决方案),编译器插入空指针,或者编译器插入某个派生类的函数的地址。我还见过一些情况,如果您提供了实现,编译器会插入该函数的地址。对于这个问题唯一正确的答案是您无法依赖于任何特定的行为。


0
我可以告诉你,“纯”的抽象类(只有纯虚函数的类)被微软(和MS VC++)用于他们的COM接口。也许他是在谈论这个。COM的“内部”表示是指向vtable的指针。在MS VC++中,纯抽象类以相同的方式实现,因此您可以使用它们来表示COM对象。显然,如果您的类具有其他虚函数,则无法简单地用COM vtable覆盖其vtable :-)

据我所知,抽象类是一个拥有一个或多个纯虚函数的类,而接口类是一个只有纯虚函数且没有成员变量的抽象类。现在,我从C#和Java中推断出了这些术语,但我在C++中并没有发现“abstract”和“interface”这些编码。 - paercebal
@paercebal 但是在C++中没有“interface” :-) “正确”的术语是“纯虚类”(我忘记写了 :-) ) - xanatos
但是在C++中没有“接口”:更具体地说,C++中不存在“interface”关键字。该概念确实存在,并且由语言支持,甚至与Java的“interface”相比还得到了扩展。 - paercebal
@paercebal 观察一下我在评论中如何格式化“接口”:-) 技巧就在那里。即使没有纯抽象类,你也可以拥有接口。接口主要存在于程序员的思维中。对于我来说,每个虚拟方法都会抛出异常并且必须重载才能执行操作的类就是一个接口。你不需要语言支持来实现多态性。你可以使用C语言的COM,而C甚至没有类,而COM是基于接口的。正如我所说,接口存在于程序员的思维中。语言只能将它们实例化标记为错误。 - xanatos

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