类的对象(使用单/多继承)有多少vptr?

Pas*_*mer 6 c++ multiple-inheritance vtable memory-layout vptr

对于其clas(子)具有单继承的对象,通常需要多少个vptrs,其基类为多个继承base1和base2.确定对象提供了多少个vpt的策略是什么,它提供了几个单继承和多继承.虽然标准没有指定vptrs,但我只是想知道一个实现如何实现虚函数.

Dav*_*eas 5

你为什么在乎?简单的答案就足够了,但我想你想要更完整的东西.

这不是标准的一部分,因此任何实现都可以按照自己的意愿自由进行,但一般的经验法则是在使用虚拟表指针的实现中,作为第0个近似值,用于动态调度,最多需要许多指向虚拟表的指针,因为有些类新的虚方法添加到层次结构中.(在某些情况下,可以扩展虚拟表,并且基本和派生类型共享一个vptr)

// some examples:
struct a { void foo(); };           // no need for virtual table
struct b : a { virtual foo1(); };   // need vtable, and vptr
struct c : b { void bar(); };       // no extra virtual table, 1 vptr (b) suffices
struct d : b { virtual bar(); };    // extra vtable, need b.vptr and d.vptr

struct e : d, b {};                 // 3 vptr, 2 for the d subobject and one for
                                    // the additional b
struct f : virtual b {};
struct g : virtual b {};
struct h : f, g {};                 // single vptr, only b needs vtable and
                                    // there is a single b
Run Code Online (Sandbox Code Playgroud)

基本上每个需要自己动态调度的类型的子对象(不能直接重用父对象)都需要自己的虚拟表和vptr.

实际上编译器将不同的vtable合并到一个vtable中.当在d函数集中添加新的虚函数时b,编译器会通过将新的插槽附加到vtable的末尾将可能的两个表合并为一个表,因此vtable d将是vtable 的扩展版本.b最后保留二进制兼容性的额外元素(即dvtable可以解释为b访问可用方法的vtable b),并且d对象将具有单个vptr.

在多重继承的情况下,事情变得有点复杂,因为每个基础需要与完整对象的子对象具有相同的布局,而不是它是单独的对象,因此将有额外的vptrs指向完整对象中的不同区域虚函数表.

最后,在虚拟继承的情况下,事情变得更加复杂,并且可能有多个vtable用于同一个完整对象,随着构造/破坏的发展,vptr会被更新(vptr总是在构造/破坏演变时更新,但没有虚拟继承vptr将指向base的vtable,而在虚拟继承的情况下,将有多个相同类型的vtable)

  • "`struct d : b { virtual bar(); }; // 额外的 vtable,需要 b.vptr 和 d.vptr`" 我认为没有任何编译器在一个类中引入多个 vptr虚拟SI。 (2认同)

cur*_*guy 5

精美的印刷品

没有指定任何关于 vptr/vtable 的东西,所以这将取决于编译器的细节,但几乎每个现代编译器都处理简单的情况(我写“几乎”以防万一)。

你被警告了。

对象布局:非虚拟继承

如果您从基类继承,并且它们有一个 vptr,那么您的类中自然会有许多继承的 vptr

问题是:编译器什么时候会向已经继承了 vptr 的类添加 vptr?

编译器会尽量避免添加多余的 vptr:

struct B { 
    virtual ~B(); 
};

struct D : B { 
    virtual void foo(); 
};
Run Code Online (Sandbox Code Playgroud)

这里B有一个vptr,所以D没有得到自己的vptr,它重用了现有的vptr;的 vtableB扩展为条目foo()。vtable forD是从vtable for中“派生”出来的B,伪代码:

struct B_vtable { 
    typeinfo *info; // for typeid, dynamic_cast
    void (*destructor)(B*); 
};

struct D_vtable : B_vtable { 
    void (*foo)(D*); 
};
Run Code Online (Sandbox Code Playgroud)

再次强调:这是对真正的 vtable 的简化,以获得想法。

虚拟继承

对于非虚拟单继承,实现之间几乎没有变化的余地。对于虚拟继承,编译器之间有更多的变化。

struct B2 : virtual A {
};
Run Code Online (Sandbox Code Playgroud)

有一个从B2*to的转换A*,所以一个B2对象必须提供这个功能:

  • 要么有A*会员
  • 要么使用 int 成员: offset_of_A_from_B2
  • 或者使用它的 vptr,通过存储offset_of_A_from_B2在 vtable 中

通常,类不会重用其虚拟基类的 vptr(但在非常特殊的情况下可以)。