动态转型(dynamic_cast)是如何实现的?

49

考虑这个简单的层次结构:

class Base { public: virtual ~Base() { } };
class Derived : public Base { };

试图将 Base* p 向下转换为 Derived* 可以使用 dynamic_cast<Derived*>(p)。我曾经认为 dynamic_cast 是通过比较在 p 中的 vtable 指针和一个 Derived 对象中的指针来工作的。

但是如果我们从 Derived 派生另一个类呢? 现在我们有:

class Derived2 : public Derived { };

在这种情况下:

Base* base = new Derived2;
Derived* derived = dynamic_cast<Derived*>(base);

尽管 Derived2 中的虚表指针与 Derived 中的虚表指针无关,我们仍然可以进行成功的向下转型。

它是如何实现的呢?dynamic_cast 如何知道 Derived2 是从 Derived 派生的(如果 Derived 是在另一个库中声明的怎么办)?

我想要了解有关此实现的具体细节(最好是在 GCC 中,其他也可以)。这个问题并不是这个问题的重复,后者没有指定实现方式。


2
它在不同的编译器中可能会有不同的实现方式,为了确保你可以阅读它们的源代码。 - PlasmaHH
3个回答

36

如何使dynamic_cast知道Derived2是从Derived派生的(如果Derived在另一个库中声明怎么办)?

答案出乎意料地简单:通过保存这种知识,dynamic_cast可以知道。

当编译器生成代码时,它会保存类层次结构的数据到某种表中,以后dynamic_cast可以查找该表。该表可以附加到虚函数表指针上,以便dynamic_cast实现轻松查找。这些类的typeid所需的数据也可以存储在其中。

如果涉及到库,则通常需要将这些类型信息结构公开在库中,就像函数一样。例如,可能会收到类似于“未定义引用‘XXX’的vtable”的链接器错误消息(而且这些错误真的很烦人!),与函数一样。


当涉及到多重继承时,会出现一些复杂情况。例如,请参见这个问题 - BlueRaja - Danny Pflughoeft
虽然在Itanium ABI的情况下没有标准化,但是知识被集成在被vtables指向的RTTI结构中。https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti - jmcarter9t

22

神奇。

开玩笑了。如果您真的想详细研究这个问题,实现它的代码在GCC的libsupc++中,它是libstdc++的一部分。

https://github.com/mirrors/gcc/tree/master/libstdc%2B%2B-v3/libsupc%2B%2B

具体来说,查找所有名称中带有tinfo或type_info的文件。

或者在此处阅读说明,那可能更容易理解:

https://itanium-cxx-abi.github.io/cxx-abi/abi.html#rtti

这详细介绍了编译器生成的类型信息格式,并应该为您提供线索,以便运行时支持找到正确的转换路径。


4
非常感谢!尤其需要关注的是第2.9.5节,其中包含了RTTI结构(又称为v-table)的描述以及继承层次结构的编码方式(当然还有描述算法本身的第2.9.7节)。 - Matthieu M.

5
动态转换如何知道Derived2是否从Derived继承而来(如果Derived是在不同的库中声明的会怎样)?
动态转换本身并不知道任何信息,它是编译器知道这些事实。虚函数表不一定只包含指向虚函数的指针。
以下是我(天真地)的做法:我的虚函数表将包含指向某些类型信息(RTTI)的指针,由dynamic_cast使用。该类型的RTTI将包含指向基类的指针,因此可以向上遍历类层次结构。进行转换的伪代码如下:
Base* base = new Derived2; //base->vptr[RTTI_index] points to RTTI_of(Derived2)

//dynamic_cast<Derived*>(base):
RTTI* pRTTI = base->vptr[RTTI_index];
while (pRTTI && *pRTTI != RTTI_of(Derived))
{
  pRTTI = pRTTI->ParentRTTI;
}
if (pRTTI) return (Derived*)(base);
return NULL;

4
的确有些幼稚,因为它没有考虑到多个基类、虚基类(这些并不好玩)和偏移适配。不过,基本思想是不错的:对象拓扑结构被编码在它的 V 表中。 - Matthieu M.
@MatthieuM。这就是我想要的:描述基本思路。我已经考虑了其他东西(除了虚拟基类,它们可以滚蛋!),但是我不想写大量无用的伪代码;-) - Arne Mertz
作为参考实现,我在libcxxrt中发现了一个令人惊讶地易读的实现,特别是与 Sebastian Redl 的链接结合起来,在Itanium ABI的2.9.5节中展示了RTTI结构的布局,尤其清晰。 - Matthieu M.
@MatthieuM。非常聪明。实际上,这个想法与此处介绍的类似,但使用多态递归来支持从__class_type_info派生的不同类型的类。感谢提供链接! - Avidan Borisov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接