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

Posted

技术标签:

【中文标题】一个类的对象(使用单/多继承)将有多少个 vptr?【英文标题】:How many vptr will a object of class(uses single/multiple inheritance) have? 【发布时间】:2011-03-21 11:59:46 【问题描述】:

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

【问题讨论】:

【参考方案1】:

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

这不是标准的一部分,因此任何实现都可以随心所欲,但一般的经验法则是,在使用虚拟表指针作为第零近似值的实现中,您需要动态调度至多与向层次结构添加新虚拟方法的类的数量一样多的指向虚拟表的指针。 (在某些情况下可以扩展虚拟表,基类型和派生类型共享一个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 中。当db 中的一组函数上添加一个新的虚函数时,编译器将通过将新插槽附加到vtable 的末尾来将潜在的两个表合并为一个表,因此d 的vtable将是 b 的 vtable 的扩展版本,在末尾具有额外的元素以保持二进制兼容性(即 d vtable 可以解释为 b vtable 以访问 b 中可用的方法),并且d 对象将有一个 vptr

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

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

【讨论】:

"struct d : b virtual bar(); ; // extra vtable, need b.vptr and d.vptr" 我认为没有任何编译器会在具有非虚拟 SI 的类中引入多个 vptr。【参考方案2】:

细则

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

您已被警告。

对象布局:非虚拟继承

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

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

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

struct B  
    virtual ~B(); 
;

struct D : B  
    virtual void foo(); 
;

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

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*A* 的转换,因此B2 对象必须提供此功能:

A* 成员 具有 int 成员:offset_of_A_from_B2 使用它的 vptr,将 offset_of_A_from_B2 存储在 vtable 中

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

【讨论】:

以上是关于一个类的对象(使用单/多继承)将有多少个 vptr?的主要内容,如果未能解决你的问题,请参考以下文章

面向对象三大特征之继承

继承.

python 面向对象专题:继承

python 面向对象专题:继承

为啥 vptr 存储为具有虚函数的类的内存中的第一个条目?

Python 基础之面向对象类的继承与多态