虚拟多重继承和转换

时间:2013-12-23 20:00:49

标签: c++ multiple-inheritance dynamic-cast downcast virtual-inheritance

我尝试创建一个继承了多个类的类,获得一个“钻石”
(D继承自B和C. B和C都继承自A 虚拟):

  

A
     / \
     B C
     \ /
     d

现在,我有一个带有链表的容器,该列表包含指向基类的指针( A )。
当我尝试对指针进行显式转换时(在typeid检查之后),我得到以下错误:
“无法将指针转换为基类”A“指向派生类”D“ - 基类是虚拟的”

但是当我使用动态投射时,似乎工作得很好 任何人都可以向我解释为什么我必须使用动态转换,为什么虚拟继承会导致此错误?

2 个答案:

答案 0 :(得分:3)

“虚拟”总是表示“在运行时确定”。虚函数位于运行时,虚拟基也位于运行时。虚拟性的全部意义在于,所讨论的实际目标是静态不可知的。

因此,无法确定在编译时为其提供虚拟基础的最派生对象,因为基础和最派生对象之间的关系不固定。你必须等到知道实际对象是什么才能决定它与基数的关系。这就是动态演员正在做的事情。

答案 1 :(得分:1)

  

当我尝试对指针进行显式转换时(在typeid检查之后)

成功typeid(x) == typeid(T)后,您知道x的动态类型,理论上您可以避免此时dynamic_cast中涉及的任何其他运行时检查。 OTOH,编译器不需要进行这种静态分析。

static_cast<T&>(x)没有向编译器传达x的动态类型确实是T的知识:前提条件较弱(T对象具有{{{ 1}}作为子对象基类)。

C ++可以提供x,仅当static_exact_cast<T&>(x)指定动态类型x的对象时才有效(而不是T派生的某种类型,与{{1}不同}})。假设T的动态类型为static_cast<T&>,此假设static_exact_cast<T&>(x)将跳过任何运行时检查并根据x对象布局的知识计算正确的地址:因为在< / p>

T

不需要运行时偏移计算,在T中,反向调整不涉及运行时偏移计算。

给出

D d;
B &br = d;

static_exact_cast<D&>(br)中需要运行时偏移量计算(除非整个程序分析显示没有派生自B &D_to_B (D &dr) { return dr; } 的类具有不同的基类D_to_B偏移量);给定

D

A struct E1 : virtual A struct E2 : virtual A struct F : E1, D, E2 子对象的布局将与D完整对象的布局不同:F子对象将处于不同的偏移量。 D所需的偏移量将由A的vtable给出(或直接存储在对象中);这意味着D_to_B不会仅仅将常量偏移作为一个简单的“静态”向上转换(在进入对象的构造函数之前,vptr没有设置,因此这样的转换无法工作;请注意构造函数中的向上转换初始列表)。

BTW,DD_to_B没有区别,因此您可以看到偏移的运行时计算可以在D_to_B (d)内完成。

考虑以下代码编写天真(假设没有花哨分析显示static_cast<B&> (d)具有动态类型static_cast):

ar

F(未知动态类型的左值)的引用中查找F f; D &dr = f; // static offset A &ar = dr; // runtime offset D &dr2 = dynamic_cast<D&>(ar); 基类主题需要对vtable(或等效项)进行运行时检查。回到A子对象需要一个非常重要的计算:

  • 使用D
  • 的vtable找出完整对象的地址(恰好是D类型的地址)
  • 再次使用vtable找出明确且公开派生的F基类A基类对象的地址

这不是一件轻而易举的事,因为Dfdynamic_cast<D&>(ar)的布局,F的vtable的布局)没有具体的了解。一切都是从vtable中获取的。所有F都知道有一个派生类F,而vtable有所有信息。

当然,C ++中没有dynamic_cast,所以你必须使用A和相关的运行时检查; static_exact_cast<>是一个复杂的函数,但复杂性涵盖了基类的情况;当给dynamic_cast赋予动态类型时,避免了基类树行走,并且测试相当简单。

结论:

您可以在dynamic_cast中命名动态类型,而dynamic_cast无论如何都是快速而简单的,或者您没有,并且确实需要dynamic_cast<T>的复杂性。