在虚函数表中,当编译器遇到类中的虚函数时,它会创建虚函数表并将虚函数地址放置在其中。继承其他类时也是如此。但是,它是否在每个类中都创建指向每个虚函数表的新指针?如果不是,那么当创建派生类的新实例并分配给基类指针时,如何访问虚函数呢?
每次创建包含虚拟函数的类,或从包含虚拟函数的类派生时,编译器都会为该类创建一个唯一的VTABLE。如果在基类中声明了一个虚拟函数,但没有覆盖它,则编译器将在派生类中使用基类版本的地址。然后将VPTR放置到类中。在简单继承中,每个对象只有一个VPTR。必须将VPTR初始化为适当VTABLE的起始地址。(这发生在构造函数中。)一旦VPTR被初始化为正确的VTABLE,对象实际上“知道”它的类型。但是,除非在调用虚拟函数的点使用自我知识,否则这种自我知识是没有价值的。当您通过基类地址调用虚拟函数(当编译器没有所有必要的信息来执行早期绑定时的情况)时,会发生特殊的事情。不像执行typical函数调用那样,它只是针对特定地址的汇编语言CALL,而是生成不同的代码来执行函数调用。
对于每个带有虚函数的类,都会创建一个虚函数表(vtable)。然后,当使用构造函数创建一个带有vtable的类的对象时,构造函数将适当的vtable复制到对象中。因此,每个对象都有指向其vtable的指针(或在多重继承的情况下,必要时是指向每个vtable的指针)。编译器知道vtable在对象中的位置,因此当需要调用虚方法时,它会输出字节码以确定vtable、查找适当的方法,并跳转到其地址。在单一继承的简单情况下,子类从父类的vtable开始,并为子类中覆盖父类方法的每个虚方法获取一个被覆盖的条目(对于在子类中不覆盖父类方法的每个虚函数,它还会获得一个新条目)。