$ 4.11 / 2州 -
“指向成员的指针”的右值 属于 cv
B
的{{1}},“其中T
是一个类 type,可以转换为rvalue 键入“指向B
类型成员的指针 cvD
,“其中T
是派生类 ({1}}的(第10条)。如果D
是 难以接近(第11条),含糊不清 (10.2)或虚拟(10.1)基类B
,一个需要这个的程序 转换是不正确的。
我的问题是为什么我们限制B
不是D
的虚拟基类?
答案 0 :(得分:5)
考虑涉及非虚拟基类的情况:
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
的指针”,您只需要一个整数偏移量{ {1}}子对象位于A
(第一个B
子对象)。
要将“A
类型的int a
指针”转换为“A
类型int a
的指针”,您只需要一个整数偏移量{ {1}}子对象位于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
的指针,因此我们根本没有足够的信息让转发效果正常。
答案 1 :(得分:1)
使用非虚拟继承,基类和派生类成员可以在内存中连续布局,首先使用基类,以便每个基类成员相对于对象的地址位于相同的位置该对象是B
还是D
。这样可以轻松地将指针指向成员B
转换为指向成员指针{ - 1}};两者都可以表示为对象地址的偏移量。
使用虚拟继承,必须通过派生对象中的指针(或等效项)访问基类成员,指示基类所在的位置。这需要向指向成员的表示添加额外的信息,以指示需要这种间接,并且在使用任何指向成员的指针时需要运行时检查。
C ++背后的一般原则是尽可能避免运行时开销。在这种情况下,选择是在对相当常见的操作进行运行时检查,而不是禁止相当模糊的转换,并且似乎在这里应用了该主体。
答案 2 :(得分:0)
非常有趣的问题。今天学到了新的东西。这是我能找到的与主题相关的内容: Casting member function pointers from derived class to virtual base class does not work