【问题标题】:C++: is a class with virtual base but without virtual functions polymorphic and has VTable?C++:是一个具有虚基但没有虚函数多态并具有 VTable 的类吗?
【发布时间】:2024-08-28 04:25:01
【问题描述】:

考虑以下代码:

#include <iostream>
#include <typeinfo>
#include <type_traits>

using namespace std;

struct A { int data; };
struct B1 : A {};
struct B2 : virtual A {};

struct Base1 : virtual A {};
struct Base2 : virtual A {};
struct Derived : Base1, Base2 {};

int main() {
  cout << sizeof(B1) << endl;
  cout << sizeof(B2) << endl;
  cout << sizeof(Derived) << endl;

  cout << std::is_polymorphic<B1>::value << endl;
  cout << std::is_polymorphic<B2>::value << endl;
  cout << std::is_polymorphic<Derived>::value << endl;
  return 0;
}

在我的系统上打印

4
8
12
0
0
0

这意味着这些类都不是多态的。但是,B1 和 B2 的大小因指针的大小而不同,该指针可能是指向 vtable 的指针。我用-fdump-class-hierarchy 运行 gcc 并得到:

Vtable for B2
B2::_ZTV2B2: 3u entries
0     4u
4     (int (*)(...))0
8     (int (*)(...))(& _ZTI2B2)

VTT for B2
B2::_ZTT2B2: 1u entries
0     ((& B2::_ZTV2B2) + 12u)

Class B2
   size=8 align=4
   base size=4 base align=4
B2 (0x0x3ca5400) 0 nearly-empty
    vptridx=0u vptr=((& B2::_ZTV2B2) + 12u)
  A (0x0x3c972d8) 4 virtual
      vbaseoffset=-12

这里有一个问题:什么是多态类? “具有 vtable”是否意味着“具有多态性并具有 RTTI”,反之亦然?如果不是,为什么它必须至少有一个虚拟函数才能是多态的,如果它无论如何都要有 vtable?

看起来多态类的定义与“具有 vtable 的类”不同,因为正如我们在上面看到的,B2 不是多态的,但有一个 vtable,而且我认为应该有一个 RTTI。在this 文档中明确指出“多态 - 即至少有一个虚函数。(这是允许生成的调度代码在类上使用 RTTI 所必需的。)”。

【问题讨论】:

  • 我的意思是,分离“多态类”和“具有 vtable 的类”的目的是什么?

标签: c++ polymorphism rtti virtual-functions virtual-inheritance


【解决方案1】:

这是std::is_polymorphic的定义

如果 T 是多态类(即声明或 至少继承一个虚函数),提供成员常量 值等于真。对于任何其他类型,值为 false。

由于没有定义虚函数,所以它会返回 false。

详细来说,我们应该区分多态性和vtables。在 C++ 中,多态确实需要 vtable,但其他非多态概念也需要。

我可以尝试解释虚拟继承,当它与多重继承一起使用时会创建一个 vtable,但 this link 做得更好。

【讨论】:

  • 是的,从标准的 POV 来看是可以的。但是这个解决方案背后的原因是什么?为什么不制作“多态类”和“具有 vtable 的类”相同的东西?
  • @yeputons 我会在我的回答中详细说明,但基本上只是因为它有一个虚拟表,并不意味着它是多态的。
【解决方案2】:

您在这里遗漏了一个细节:虚拟表是一个实现细节。结果:

  • 标准将多态类定义为可以在多态意义上使用的类:即,可以覆盖行为的地方
  • 编译器使用虚拟表来实现标准规定的某些特性/行为,这些特性/行为恰好包括多态类和虚拟基。

因此,是的我知道的编译器(MSVC 和遵循 Itanium ABI 的编译器,例如 gcc、icc 和 Clang)将使用虚拟表来提供dynamic_cast 在虚基的存在...不,这与一个类是否是多态的无关。

不过,Prefer Composition Over Inheritance 意味着如果没有可以覆盖的行为,则几乎没有理由从类继承。

【讨论】:

  • 那么,虚拟基础顺便让dynamic_cast 在某些编译器上工作?这似乎是未定义的行为。多态性的标准思想——使dynamic_cast 格式良好的原因——仅指具有至少一个虚函数,而不是虚基,因此这样做的编译器和依赖它的用户似乎很不负责任。
  • @underscore_d:我……很有趣。我必须检查标准是否为这种情况做出了特殊规定,但至少 cppreference 页面上没有这样的事情,所以我不抱希望。
  • 为了清楚起见,我只想用dynamic_cast 向下转换。其他情况可以在没有 RTTI 的情况下工作 - 但在这种情况下,更合适的 static_cast 甚至只是隐式转换也可以。
【解决方案3】:

对于那些对继承提供的 VTable 效果感到好奇的人。

以下示例

struct VB{
   // virtual int f(){}
};

struct IA:virtual VB{};

int main(int argc, char** argv) {
    VB* o = new IA;

    IA* iap = dynamic_cast<IA*>(o);

}

不会编译(g++ 4.8):

main.cpp:在函数“int main(int, char**)”中:main.cpp:26:34:错误: 不能 dynamic_cast ‘o’(‘struct VB*’类型)输入‘struct IA*’ (源类型不是多态的) IA* iap = dynamic_cast(o);

虽然取消注释 int f() 成员会得到所需的结果。

【讨论】:

  • 这可能是编译器的错误
  • @BЈовић 不,这是根据规范的预期行为。
最近更新 更多