1

在 C++ 中,在运行时没有类表示,但我总是可以在派生类中调用重写的虚拟方法。那个被覆盖的方法保存在 vtable 的哪里?这是一段代码来演示:

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 在该内存布局中保存在哪里(如果它们被保存的话)?

4

3 回答 3

0

dClass的对象D将只有一个指向class的VMTD的指针,其中包含指向 D::f 的指针。由于 B1:f 和 B2::f 只能从 D 类的范围内静态调用,因此对象不需要d保留指向这些重写方法的动态指针。

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

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

于 2015-09-13T10:45:26.727 回答
0

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

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

这是一个简短的示例:

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::foo实现的A,并生成对A::fooinside的调用C::bar

* vtables 不是实现虚拟调度的唯一方法。C++ 标准不要求使用 vtables。

于 2015-09-13T10:48:23.897 回答
0

尽管 C++ 标准中没有任何规定,但每个已知的 C++ 实现都使用相同的方法:每个至少具有虚函数的类都有一个 vptr(指向 vtable 的指针)。

您没有提到虚拟继承,这是一种不同的,更微妙的继承关系;非虚拟继承是基类子对象和派生类之间的简单排他关系。我将假设此答案中的所有继承关系都不是虚拟的。

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

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

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

第一类是主要的基类:在Dvptr 中从B1点到一个完整的 vtable for D,该 vtable 具有 的所有虚函数D。来自非主基类的每个 vptr 都指向一个辅助 vtable D:一个仅具有来自该辅助基类的虚函数的 vtable。

的构造函数D必须初始化类实例的每个 vptr 以指向适当的 vtable D

于 2015-09-13T23:53:46.757 回答