6

一个对象的 clas(child) 与一个多重继承 base1 和 base2 的基类具有单继承关系,通常需要多少个 vptr。确定一个对象提供了多少个 vptr 的策略是什么,它具有一对单继承和多继承。虽然标准没有具体说明 vptrs 但我只想知道一个实现是如何实现虚拟功能的。

4

2 回答 2

7

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

这不是标准的一部分,因此任何实现都可以随心所欲地做,但一般的经验法则是,在使用虚拟表指针作为第零近似值的实现中,您最多需要作为许多指向虚拟表的指针,因为有向层次结构添加新虚拟方法的类。(在某些情况下可以扩展虚拟表,并且基类型和派生类型共享一个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(); };    // 1 vtable, d extends b's vtable

    struct e : d, b {};                 // 2 vptr, 1 for the d and 1 for b                                      
    struct f : virtual b {};            // 1 vptr, f reuse b's vptr to locate subobject b
    struct g : virtual b {};            // 1 vptr, g reuse b's vptr to locate subobject b
    struct h : f, g {};                 // 2 vptr, 1 for f, 1 for g
                                        // h can locate subobject b using f's vptr

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

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

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

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

于 2010-07-27T09:07:02.257 回答
5

精美的印刷品

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

你被警告了。

对象布局:非虚继承

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

问题是:编译器何时将 vptr 添加到已继承 vptr 的类中?

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

struct B { 
    virtual ~B(); 
};

struct D : B { 
    virtual void foo(); 
};

这里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*); 
};

小字,再次:这是一个真正的 vtable 的简化,以获得这个想法。

虚拟继承

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

struct B2 : virtual A {
};

存在从B2*to的转换A*,因此B2对象必须提供以下功能:

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

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

于 2012-08-18T01:58:22.650 回答