$4.11/2 个州 -
“指向cv
B
类型成员的指针”类型的右值,其中是类类型,可以转换为“指向cv类型 成员的指针”类型的右值,其中是 的派生类(第 10 条)。如果是 的不可访问(第 11 条)、模棱两可(10.2)或虚拟(10.1)基类 ,则需要此转换的程序格式错误。T
B
D
T
D
B
B
D
我的问题是为什么我们有B
不能成为虚拟基类的限制D
?
$4.11/2 个州 -
“指向cv
B
类型成员的指针”类型的右值,其中是类类型,可以转换为“指向cv类型 成员的指针”类型的右值,其中是 的派生类(第 10 条)。如果是 的不可访问(第 11 条)、模棱两可(10.2)或虚拟(10.1)基类 ,则需要此转换的程序格式错误。T
B
D
T
D
B
B
D
我的问题是为什么我们有B
不能成为虚拟基类的限制D
?
考虑一个涉及非虚拟基类的情况:
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
andC
并且每个都有一个A
子对象。
指向成员变量的指针通常实现为距对象开头的整数偏移量。int a
在这种情况下,对象中的整数偏移量A
为零。int a
因此,“类型的指针A
”可能只是零的整数偏移量。
要将“类型的指针”转换为“类型int a
的A
指针”,您只需要到位于(第一个子对象)中的子对象的整数偏移量。int a
B
A
B
A
要将“int a
类型指针”转换为“类型A
指针”,您只需要一个指向位于(第二个子对象)中的子对象的整数偏移量。int a
C
A
C
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
nor的反向指针C
,所以我们根本没有足够的信息让 downcast 工作。
使用非虚拟继承,基类和派生类成员可以在内存中连续布局,基类在前,这样每个基类成员相对于对象的地址位于相同的位置。是一个B
或一个D
。这使得将指向成员的指针转换为指向成员的指针变得B
容易D
;两者都可以表示为对象地址的偏移量。
使用虚拟继承,必须通过派生对象中的指针(或等价物)访问基类成员,指示基类所在的位置。这将需要在指向成员的表示中添加额外的信息以指示需要这种间接性,并且在使用任何指向成员的指针时需要进行运行时检查。
许多 C++ 背后的一般原则是尽可能避免运行时开销。在这种情况下,选择是在对相当常见的操作进行运行时检查与不允许相当模糊的转换之间进行选择,并且似乎在此处应用了该主体。
真的很有趣的问题。今天学到了新东西。这是我可以找到的与主题相关的内容: 将成员函数指针从派生类转换为虚拟基类不起作用