2

我最近一直在学习一些关于虚拟表的更“深入”的东西,我想到了这个问题。

假设我们有这个样本:

class A {
 virtual void foo();
}

class B : public A {
 void foo();
}

在这种情况下,据我所知,每个类都会存在一个 vtable,并且调度将非常简单。

现在假设我们将 B 类更改为如下所示:

class B : public C, public A {
  void foo();
}

如果类 C 有一些虚方法,则 B 的分派机制会更复杂。对于两个继承路径 BC、BA 等,可能会有 2 个 vtable。

从我到目前为止所学到的东西看来,如果代码库函数中还有其他地方是这样的:

void bar(A * a) {
  a->foo();
}

它现在需要使用更复杂的调度机制进行编译,因为在编译时我们不知道“a”是指向 A 还是 B。

现在的问题。假设我们将新的 B 类添加到我们的代码库中。在我看来,它似乎不太可能需要在使用指向 A 的指针的任何地方重新编译代码。

据我所知,vtables 是由编译器创建的。但是,链接器可能在重定位期间解决了此修复问题吗?在我看来,我确实找不到任何证据可以确定,因此现在就去睡觉:)

4

2 回答 2

3

在内部void bar(A * a),指针肯定指向一个A对象。该A对象可能是诸如 a 之类的其他对象的子对象B,但这无关紧要。是自包含的A,并且有自己的 vtable 指针,它链接到foo.

当从B *to的转换A *发生时,例如当bar使用 a 调用时B *,可能会在 中添加一个常量偏移量B *以使其指向A子对象。如果每个对象中的第一件事是 vtable,那么这也将设置指向Avtable 的指针。对于单继承,调整是不必要的。

以下是典型实现的内存外观:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |

B vt: | ptrs to methods of C (with B overrides) | ptrs to methods of B |
AB vt: | ptrs to methods of A (with B overrides) |

(请注意,通常AB vt仍然是 的一部分B vt;它们在内存中是连续的。然后ptrs to methods of B可以继续ptrs to methods of A。为了格式清晰,我只是这样写的。)

当您将 a 转换B *为 anA *时,您将从此开始:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
^ your B * pointer value

对此:

| ptr to B vt | members of C | members of B | ptr to AB vt | members of A |
                                            ^ your A * pointer value

使用static_castfrom A *toB *会将指针向后移动,向另一个方向移动。

于 2013-06-20T22:50:47.823 回答
0

不,不需要重新编译仅依赖于A.

这实际上是从典型的现代 C++ 编译器所遵循的“独立翻译”原则直接得出的。您可以编写所有仅依赖于的代码A,使其永远不会包含任何提及B. 这意味着任何更改B都不会触发任何A特定代码的重新编译。这反过来意味着遵循“独立翻译”原则的 C++ 编译器必须以这样一种方式实现简单继承、多重继承、虚拟分派、分层转换等,使得B特定代码中的任何更改都不需要重新编译任何A- 特定代码。

如果不是这样,典型的 C++ 编译器的体系结构就必须有很大的不同。

于 2013-06-20T23:21:27.090 回答