在多重继承中,被重写的虚方法保存在c++的虚表中



在c++中,在运行时没有类表示,但我总是可以在派生类中调用重写的虚方法。在虚表中保存的重写方法在哪里?下面是一段代码来演示:

struct B1 {
 virtual void f() { ... }
};
struct B2 {
 virtual void f() { ... }
 virtual void g() { ... }
};
struct D : B1, B2 {
 void f() { ... }
 virtual void h() { ... }
};

D类对象的内存布局是什么?B1::f和B2::f保存在内存布局中(如果它们被保存的话)?

D的对象d将只有一个指向类D的VMT的指针,该指针将包含指向D::f的指针。因为B1:f和B2::f只能从D类的作用域中静态调用,所以对象d不需要保持指向那些被覆盖的方法的动态指针。

这当然没有在标准中定义,这只是编译器通常的/逻辑实现。

实际上情况更复杂,因为D类的VMT合并了B1和B2类的VMT。但是,无论如何,在创建类B1的对象之前,不需要动态调用B1::f。

当编译器使用虚分派的虚表方法*时,被覆盖的成员函数的地址存储在定义该函数的基类的虚表中。

每个类都可以访问其所有基类的变量。这些变量存储在类本身的内存布局之外。每个具有虚成员函数的类,无论是声明的还是继承的,都有一个指向自己虚函数表的指针。调用重写的成员函数时,需要提供希望调用其成员函数的基类的名称。编译器知道所有类的虚表,因此它知道如何定位基类的虚表,在编译时进行查找,并直接调用成员函数。

下面是一个简短的例子:

struct A {
    virtual void foo()   { cout << "A"; }
};
struct B : public A { }; // No overrides
struct C : public B {
    virtual void foo()   { cout << "C"; }
    void bar()           { B::foo(); }
};

演示。

在上面的例子中,编译器需要查找B::foo,它没有在B类中定义。编译器查阅其符号表,发现B::fooA中实现,并在C::bar中生成对A::foo的调用。

* vtables并不是实现虚拟调度的唯一方法。c++标准不要求使用变量表

尽管c++标准中没有强制要求,但每个已知的c++实现都使用相同的方法:每个至少具有虚函数的类都有一个vptr(指向虚函数表的指针)。

你没有提到虚拟继承,这是一种不同的、更微妙的继承关系;非虚继承是基类子对象和派生类之间的简单独占关系。

这里假设我们从至少具有虚函数的类派生。

在单继承的情况下,重用基类中的 vptr 。(不重用它只会浪费空间和运行时间。)基类称为"主基类"。

在多重继承的情况下,派生类的布局包含每个基类的布局,就像C语言中结构体的布局包含每个成员的布局一样。D的布局是B1然后B2(实际上是任意顺序,但源代码通常保持顺序)。

第一个类是主基类:在D中,从B1指向D的完整虚函数表,该虚函数表包含D的所有虚函数。来自非主基类的每个vptr都指向D的辅助虚函数表:该虚函数表仅包含来自该辅助基类的虚函数。

D的构造函数必须初始化类实例的每个vptr,使其指向D的适当虚函数表。

最新更新