C++ 中的复杂dynamic_cast

Complex dynamic_cast in c++

本文关键字:cast dynamic 复杂 C++      更新时间:2023-10-16

我在C++有以下情况:

  • 抽象基类Abstract1Abstract2。它们是无关的。
  • Abstract1Abstract2派生Foo

我在一个编译单元中,我没有关于类Foo的信息(没有声明,没有定义)。只有Abstract1Abstract2是已知的。(实际上,Foo甚至在DLL中定义)

dynamic_cast允许从Abstract1*铸造到Abstract2*吗?这是一个标准吗?

你描述的是所谓的交叉投射。对于dynamic_cast<T>(v),标准在 [expr.dynamic.cast]/8 中指定

如果CT指向或引用的类类型,则运行时 检查逻辑执行如下:

  • 如果在v指向(引用)的最派生对象中,v点(引用)到C对象的公共基类子对象[..]

  • 否则,如果v指向(引用)派生最多的对象的public基类子对象,以及派生最多的对象的类型 有一个基类,类型为 C,它是明确和public的, 结果点(引用)到派生最多的子对象的C子对象 对象。

即使没有关于Foo在包含演员表的翻译单元中存在的信息,这也可以工作。

你也应该看看这个问题。

是的,它会起作用。

dynamic_cast是基于RTTI的。RTTI在此处提供的信息足以确定指向对象的实际动态类型。根据定义,RTTI 是一个运行时概念,指向对象的动态类型也是如此(事实上,Foo 的定义在编写所述强制转换的编译单元中不可用,这是一个编译时概念,在这里无关紧要)。

  • 如果指向的对象实际上是 Foo,则dynamic_cast将在运行时成功。
  • 如果它不是指向从 Abstract2 派生的对象的指针,它将失败(返回空指针)。

dynamic_cast的一个可能的实现是在对象的内存布局的开头查找一个特殊成员(或者它可以沿着向量表存储)。此结构可以包含一个值,该值标识对象的动态类型。在某个地方,编译器会生成一个静态表,复制有关程序继承图的所有信息。在运行时,强制转换将提取实例的类型标识符,并根据静态表进行检查。如果此标识符引用从 Abstract2 派生的类型,则强制转换是有意义的(并且代码可以返回指向对象Abstract2接口的正确偏移量指针)。

即使是这种朴素的实现也不需要编译单元中的Foo知识,其中演员表被写了。

对于此代码:

void func(Abstract1* a1)
{
    Abstract2* a2 = dynamic_cast<Abstract2*>(a1);
    ...
}

你在问:

如果a1指向Foo对象,动态强制转换会返回有效的对象指针吗?

答案是肯定的:

  • 在运行时,动态转换会将a1的 V-Table 标识为 V-Table of class Foo
  • 由于class Foo继承自class Abstract2,动态强制转换将返回一个有效的指针。

好吧,你可以简单地尝试一下

#include <cassert>
struct IBase1
{
    virtual void foo() = 0;
    virtual ~IBase1() {}
};
struct IBase2
{
    virtual void bar() = 0;
    virtual ~IBase2() {}
};
struct Derived : IBase1, IBase2
{
    void foo() {}
    void bar() {}
};
int main()
{
    Derived d;
    IBase1* ptr = &d;
    assert(dynamic_cast<IBase2*>(ptr));
    assert(dynamic_cast<Derived*>(ptr));
}
// Compiles successfully

这是证据:

[C++11: 5.2.7/8]: 如果CT指向或引用的类类型,则运行时检查按如下逻辑执行:

    如果在派生对象中
  • v 指向(引用)的最派生对象中,v指向C对象的public基类子对象,并且如果只有一个类型为 C 的对象从指向(引用)的子对象派生v则结果点(引用)到该C对象。
  • 否则,如果v指向(引用)派生最多的对象的公共基类子对象,并且派生最多的对象的类型具有类型为 C 的基类,该基类明确且public,则结果指向(引用)派生最多的对象的C子对象。

  • 否则,运行时检查将失败

通俗地说,我们称之为交叉转换

语言没有规定在"当前"翻译单元中知道派生最多的对象的类型;由实现来使其工作,并且在常见的"虚拟表"模型中,确实如此。