C++ 虚拟多重继承与转换
我尝试创建一个继承多个类的类,如下所示,得到一个“菱形”C++ 虚拟多重继承与转换,c++,multiple-inheritance,dynamic-cast,downcast,virtual-inheritance,C++,Multiple Inheritance,Dynamic Cast,Downcast,Virtual Inheritance,我尝试创建一个继承多个类的类,如下所示,得到一个“菱形” (D继承自B和C。B和C实际上都继承自A): A /\ B C \/ D 现在,我有了一个带有链表的容器,其中包含指向基类(a)的指针。 当我尝试对指针进行显式转换时(在typeid检查之后),我得到了以下错误: 无法将指向基类“a”的指针转换为指向派生类“D”的指针--基类是虚拟的“ 但当我使用动态铸造时,它似乎工作得很好。 有人能给我解释一下为什么我必须使用动态强制转换,为什么虚拟继承会导致这个错误吗?“虚拟”总是指“在运行时确定”
(D继承自B和C。B和C实际上都继承自A):
A
/\
B C
\/
D 现在,我有了一个带有链表的容器,其中包含指向基类(a)的指针。
当我尝试对指针进行显式转换时(在typeid检查之后),我得到了以下错误:
无法将指向基类“a”的指针转换为指向派生类“D”的指针--基类是虚拟的“
但当我使用动态铸造时,它似乎工作得很好。
有人能给我解释一下为什么我必须使用动态强制转换,为什么虚拟继承会导致这个错误吗?“虚拟”总是指“在运行时确定”。虚拟函数位于运行时,虚拟基也位于运行时。虚拟性的全部意义在于,所讨论的实际目标不是静态可知的 因此,不可能确定在编译时为其提供虚拟基的最派生对象,因为基和最派生对象之间的关系不是固定的。您必须等到知道实际对象是什么,才能确定它相对于基准的位置。这就是动态演员正在做的 当我尝试对指针进行显式转换时(在typeid检查之后) 成功执行
typeid(x)==typeid(T)
后,您知道x
的动态类型,并且理论上您可以避免在该点执行动态\u cast
中涉及的任何其他运行时检查。OTOH,编译器不需要做这种静态分析
static\u cast(x)
不会向编译器传达x
的动态类型实际上是T
:前提条件较弱(即T
对象将x
作为子对象基类)
C++可以提供一个static\u-exact\u-cast(x)
,它只有在x
指定了一个动态类型T
的对象时才有效(而不是从T
派生的某种类型,不同于static\u-cast
)。通过假设x
的动态类型为T
,这个假设的static\u exact\u cast(x)
,将跳过任何运行时检查,并根据T
对象布局的知识计算正确的地址:因为在
D d;
B &br = d;
无需计算运行时偏移量,在static\u exact\u cast(br)
中,反向调整不涉及运行时偏移量计算
给定
B &D_to_B (D &dr) {
return dr;
}
在D_to_B
中需要计算运行时偏移量(除非整个程序分析表明从D
派生的类与基类a
的偏移量不同);给定
F
的D
子对象的布局将不同于D
完整对象的布局:a
子对象将位于不同的偏移位置。D_到_B
所需的偏移量将由D
的vtable给出(或直接存储在对象中);这意味着D_to_B
将不仅仅涉及一个常量偏移量作为简单的“静态”向上转换(在进入对象的构造函数之前,没有设置vptr,因此这种转换无法工作;请注意构造函数初始列表中的向上转换)
顺便说一句,D_到_B(D)
与static_cast(D)
没有什么不同,因此您可以看到偏移量的运行时计算可以在static_cast
内完成
简单地考虑以下代码编译(假设没有花哨的分析显示ar
具有动态类型F
):
F;
D&dr=f;//静态偏移量
A&ar=dr;//运行时偏移量
D&dr2=动态投影(ar);
从对D
(未知动态类型的左值)的引用中查找A
基类主题需要对vtable(或等效项)进行运行时检查。返回到D
子对象需要一个非平凡的计算:
- 使用
vtable查找完整对象的地址(恰好是A
类型)F
- 再次使用vtable查找
的明确且公开派生的f
基类子对象的地址D
dynamic_cast(ar)
静态地不知道关于F
的任何具体内容(F
的布局,以及F
的vtable的布局);所有内容都是从vtable获取的。所有dynamic_cast
都知道有一个派生类a
,vtable拥有所有信息
当然,C++中没有<代码> StasyStAcExcTyTrase<代码>,因此必须使用<代码>动态DeCysCase<代码>,并使用相关的运行时检查;code>dynamic_cast是一个复杂的函数,但复杂度涵盖了基类情况;当动态类型被赋予dynamic\u cast
时,避免了基类树遍历,测试也相当简单
结论:
要么在
dynamic\u cast
中命名动态类型,要么在dynamic\u cast
中命名动态类型既快速又简单,要么不命名,而且确实需要dynamic\u cast
的复杂性。考虑修复您的设计。:)@每次我看到一篇关于指针铸造等的帖子,人们都建议修改设计。但是,如果我想使用一个通用容器,我该如何做才能有所不同?@davidtzoor让您的基类提供一个方便的抽象接口,供派生类实现。那么你就不需要知道具体的类型了。@davidzoor问题不在于指针或包含的类型。问题在于可怕的死亡钻石本身。(在理想情况下,继承在此时不可见。)
struct E1 : virtual A
struct E2 : virtual A
struct F : E1, D, E2
F f;
D &dr = f; // static offset
A &ar = dr; // runtime offset
D &dr2 = dynamic_cast<D&>(ar);