我尝试创建一个继承了多个类的类,获得一个“钻石”
(D继承自B和C. B和C都继承自A 虚拟):
A
/ \
B C
\ /
d
现在,我有一个带有链表的容器,该列表包含指向基类的指针( A )。
当我尝试对指针进行显式转换时(在typeid检查之后),我得到以下错误:
“无法将指针转换为基类”A“指向派生类”D“ - 基类是虚拟的”
但是当我使用动态投射时,似乎工作得很好 任何人都可以向我解释为什么我必须使用动态转换,为什么虚拟继承会导致此错误?
答案 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,D
与D_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
D
类型的地址)
F
基类A
基类对象的地址这不是一件轻而易举的事,因为D
对f
(dynamic_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>
的复杂性。