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
子对象需要一个非平凡的计算:

  • 使用
    A
    vtable查找完整对象的地址(恰好是
    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);