子类中的虚函数表是如何生成的

Ant*_*cic 0 c++ polymorphism assembly vtable

所以我知道在 C++ 中,虚拟方法适用于存储在表中的每个类,并且每个实例都有一个指向该表的指针。所以我的问题是子类表是什么样的。我将提供一个汇编指令:

vtable for Derived:
    .long   0
    .long   _typeinfo for Derived
    .long   _Derived::set()
    .globl  _vtable for Base
    .section    .rdata$vtable for Base,"dr"
    .linkonce same_size
    .align 4
Run Code Online (Sandbox Code Playgroud)

因此,从这里我可以看到 Derived 有一个虚拟方法set(),但令我烦恼的部分是 Base 的 vtable。Derived vptr 是否保存指向 Base vptr 的指针,还是存储在 Derived 的 vtable 内。我注意到,在代码中,编译器仅将 vtable 存储在对象的 0 地址处,一次存储在 Base 构造函数中,一次存储在 Derived 中。为什么vtable没有被覆盖?

PS我不太明白指令

编辑:

class Base{
   virtual void print() {
       printf("Base");
   }
}

class Derived : Base{
   virtual void print(){
       printf("Derived");
   }
}
Run Code Online (Sandbox Code Playgroud)

Jas*_*ent 5

简单的答案是:这取决于编译器。该标准没有指定应如何实现,仅指定其行为方式。

实际上,实现往往只是有一个派生 vtable,它以与基 vtable 相同的结构开始,然后在末尾附加派生类方法(也就是说派生类中的新方法,而不是重写)。

vtable指针只是指向整个表的开头。如果通过基指针类型访问对象,那么任何人都不应查看基类方法末尾之外的内容。

如果指针是派生类型,则同一指针将允许访问表中派生类中声明的虚拟方法。

附录:多重继承

多重继承遵循相同的基本概念,但由于显而易见的原因很快就会变得复杂。但有一个重要特征需要牢记。

多重派生类的每个基类都有一个 vtable 指针,指向不同的 vtable 或同一 vtable 中的不同位置(取决于实现)。

但重要的是要记住它是每个对象的每个直接基类一个。

因此,如果您有一个包含int数据之一和三个直接基类的多重派生类,则每个对象的大小实际上为 16 字节(在 32 位系统上;在 64 位系统上更多)。每个 vtable 指针有 4 个,int每个 vtable 指针有 4 个。当然,还有每个基类本身的大小。

这意味着在 C++ 中,接口并不便宜。(显然,C++ 中没有真正的接口,但没有数据且只有纯虚方法的基类可以模拟它。)每个此类接口的成本相当于每个对象的指针大小

在 C# 和 Java 等语言中,接口是语言的一部分,有一种略有不同的机制,所有接口都通过单个 vtable 指针进行路由。这稍微慢一些,但意味着每个对象只有一个 vtable 指针,无论实现了多少接口。

出于设计原因,我仍然会遵循 C++ 中的接口样式方法,但始终要注意这种额外的开销。

(这些甚至都没有涉及虚拟继承。)