【发布时间】:2021-07-18 17:52:59
【问题描述】:
我对@987654321@ 很感兴趣,这件事给我带来了神秘感。让我们以virtual inheritance 为例:
struct Base {
virtual void v() { std::cout << "v"; }
};
struct IntermediateDerivedFirst : virtual Base {
virtual void w() { std::cout << "w"; }
};
struct IntermediateDerivedSecond : virtual Base {
virtual void x() { std::cout << "x"; }
};
struct Derived : IntermediateDerivedFirst, IntermediateDerivedSecond {
virtual void y() { std::cout << "y"; }
};
最后,Derived 应该是这样的:
--------
|[vtable]| -----> [ vbase offset 20 ]
|[vtable]|--- [ top offset 0 ]
|[vtable]|- | [ Derived typeinfo ]
-------- || [ IntermediateDerivedFirst::w() ]
|| [ Derived::y() ]
||
|----> [ vbase offset 12 ]
| [ top offset -8 ]
| [ Derived typeinfo ]
| [ IntermediateDerivedSecond::x()]
|
-----> [ vbase offset 0 ]
[ top offset -20 ]
[ Derived typeinfo ]
[ Base::v() ]
所以,从字面上看,virtual 继承将vtable 移动到最后,正如我们所见——vtables 用于IntermediateDerivedFirst,IntermediateDerivedSecond 不包含Base 的地址的v() 方法。好的,那么,我们可以看到该类很少有vtables。
让我们考虑一个代码:
IntermediateDerivedFirst* fb = new Derived;
fb->v();
delete fb;
这个调用仍然有效,然而,vtable for IntermediateDerivedFirst 没有关于 v() 方法的信息,它似乎在这里使用了一些魔法,它使用第三个 vtable 指针调用v()。
那么,编译器如何选择需要的vtable指针来获取被调用函数的地址呢?
【问题讨论】:
-
这是一个实现细节。一种可能性是虚函数调用包含一个偏移量,以便“this”正确指向
Base的一个副本(或任何实现虚函数的类)。 -
您可能对 Lippman 的“Inside the C++ Object Model”一书感兴趣。它深入探讨了此类实现细节。
-
是什么让你相信
Derived的布局和你描述的一样?你是怎么确定的?这对我来说似乎不太合理——通常,最派生类的 vtable 包含指向所有基类的虚函数的指针。所以我完全期望IntermediateDerivedFirst实例的vtable包含一个指向Base::v实现的指针。 -
顺便说一下,
delete fb;在您的示例中表现出未定义的行为,通过指向其基类的指针删除没有虚拟析构函数的类的实例。 -
抱歉,提供赏金并不会改变 vtables 的布局(或者甚至,理论上,vtables 的存在,因为虽然是一种常见的实现方法,但标准不需要 vtables)是高度特定于编译器。这意味着您提出的问题没有规范的答案。 C++ 标准根本不需要您所说的“
Derived应该看起来像”。如果您正在寻找特定于特定编译器的答案,那么您需要识别该编译器,并相应地关注问题/标签。
标签: c++ inheritance virtual