考虑一个涉及非虚拟基类的情况:
class A { int a; }
class B : public A { int b; }
class C : public A { int c; }
class D : public B, public C { int d; }
这是一个可能的内存布局:
+-------------+
| A: int a; |
+-------------+
| B: int b; |
+-------------+
| A: int a; |
+-------------+
| C: int c; |
+-------------+
| D: int d; |
+-------------+
D 最终有两个 A 子对象,因为它继承自 B 和 C 并且每个都有一个 A 子对象。
指向成员变量的指针通常实现为距对象开头的整数偏移量。在这种情况下,int a 在 A 对象中的整数偏移量为零。所以“指向int a 类型A 的指针”可能只是一个零整数偏移量。
要将“指向int a 类型A”的指针转换为“指向int a 类型B”的指针,您只需要一个整数偏移量到位于B 中的A 子对象(第一个A 子对象)。
要将“指向A 类型的int a 的指针”转换为“指向int a 类型的C 的指针”,您只需要到位于C 中的A 子对象的整数偏移量(第二个A 子对象)。
由于编译器知道B 和C 相对于A 的位置,因此编译器有足够的信息来说明如何从A 向下转换为B 或C。
现在考虑一个涉及虚拟基类的情况:
struct A { int a; }
struct B : virtual public A { int b; }
struct C : virtual public A { int c; }
struct D : public B, public C { int d; }
可能的内存布局:
+-------------+
| B: ptr to A | ---+
| int b; | |
+-------------+ |
| C: ptr to A | ---+
| int c; | |
+-------------+ |
| D: int d; | |
+-------------+ |
| A: int a; | <--+
+-------------+
虚拟基类通常通过使B 和C(实际上派生自A)包含一个指向单个A 子对象的指针来实现。需要指向A 子对象的指针,因为A 相对于B 和C 的位置不是恒定的。
如果我们只有一个“指向int a 类型为A 的指针”,我们将无法将其转换为“指向int a 类型B 的指针”,因为B 和 C 子对象可以相对于 A 有所不同。 A 没有指向 B 和 C 的反向指针,所以我们根本没有足够的信息让 downcast 工作。