我最近一直在学习关于虚拟桌子的更多“深入”的事情,我想到了这个问题。
假设我们有这个样本:
class A {
virtual void foo();
}
class B : public A {
void foo();
}
在这种情况下,根据我所知,每个班级都会有一个vtable存在,并且发送将非常简单。
现在假设我们将B类更改为:
class B : public C, public A {
void foo();
}
如果C类有一些虚拟方法,B的调度机制会更复杂。两个继承路径B-C,B-A等可能有2个vtable。
从我到目前为止所学到的,似乎代码库函数中还有其他地方如下:
void bar(A * a) {
a->foo();
}
现在需要使用更复杂的调度机制进行编译,因为在编译时我们不知道“a”是否是指向A或B的指针。
现在回答这个问题。假设我们将新的B类添加到我们的代码库中。我似乎不太可能需要在使用指针A的地方重新编译代码。
据我所知,vtables是由编译器创建的。但是,有可能在重定位期间链接器可以解决此修复问题吗?对我来说似乎很难找到任何可以确定的证据,因此现在就去睡觉了。)
答案 0 :(得分:3)
在void bar(A * a)
内,指针肯定是A
个对象。 A
对象可能是B
之类的其他对象的子对象,但这是无关紧要的。 A
是自包含的,并且有自己的vtable指针,可链接到foo
。
当发生从B *
到A *
的转换时,例如使用bar
调用B *
时,可能会向{{1}添加常量偏移量使它指向B *
子对象。如果每个对象中的第一个东西都是vtable,那么这也会将指针设置为A
vtable。对于单继承,不需要进行调整。
以下是典型实现的内存:
A
(请注意,| 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
转换为B *
后,您可以从中获取:
A *
到此:
| 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_cast
的{{1}}会将指针向后移动,向另一个方向移动。
答案 1 :(得分:0)
不,没有必要重新编译仅依赖于A
的代码。
这实际上紧接着"独立翻译"的原则,这是典型的现代C ++编译器所遵循的。您可以通过这种方式编写仅依赖于A
的所有代码,它永远不会包含任何提及B
的定义。这意味着B
中的任何更改都不会触发任何A
特定代码的重新编译。这反过来意味着C ++编译器遵循"独立翻译"的原则。必须实现简单继承,多继承,虚拟分派,分层转换等,以便B
特定代码中的任何更改都不需要重新编译任何A
特定代码。
如果情况并非如此,那么典型C ++编译器的架构必须有很大不同。