我在 C++ 中有以下情况:
- 抽象基类
Abstract1
和Abstract2
. 他们是无关的。 Foo
派生自Abstract1
和的类Abstract2
我在一个编译单元中,我没有关于类的信息Foo
(没有声明,没有定义)。只有Abstract1
和Abstract2
是已知的。(实际上,Foo 甚至被定义在一个 DLL 中)
dynamic_cast 是否允许从Abstract1*
to 转换Abstract2*
?这是一个标准吗?
我在 C++ 中有以下情况:
Abstract1
和Abstract2
. 他们是无关的。Foo
派生自Abstract1
和的类Abstract2
我在一个编译单元中,我没有关于类的信息Foo
(没有声明,没有定义)。只有Abstract1
和Abstract2
是已知的。(实际上,Foo 甚至被定义在一个 DLL 中)
dynamic_cast 是否允许从Abstract1*
to 转换Abstract2*
?这是一个标准吗?
你所描述的是所谓的交叉演员。对于dynamic_cast<T>(v)
,标准在 [expr.dynamic.cast]/8 中指定
如果
C
是T
指向或引用的类类型,则运行时检查在逻辑上执行如下:
如果在指向(引用)的最派生对象中
v
,v
指向(引用)对象的公共基类子C
对象 [..]否则,如果
v
指向(引用)到最派生对象的public
基类子对象,并且最派生对象的类型具有类型为 的基类,即C
明确且public
,则结果指向(引用)到C
大多数派生对象。
即使没有关于Foo
' 在包含演员表的翻译单元中存在的信息,这也将起作用。
你也应该看看这个问题。
是的,它会起作用。
dynamic_cast
基于RTTI。RTTI 在这里提供的信息足以确定所指向对象的实际动态类型。根据定义,RTTI 是一个运行时概念,指向对象的动态类型也是如此(Foo 的定义在编写所述转换的编译单元中不可用的事实是一个编译时概念,这里没有相关性)。
一个可能的实现dynamic_cast
是在对象的内存布局的开头查找一个特殊成员(或者它可以存储在 v-table 中)。此结构可以包含一个值,该值标识对象的动态类型。在某个地方,编译器会生成一个静态表,复制有关程序继承图的所有信息。在运行时,强制转换将提取实例的类型标识符,并根据静态表检查它。如果此标识符引用派生自 Abstract2 的类型,则强制转换是有意义的(并且代码可以正确返回指向Abstract2
对象接口的偏移量的指针)。
即使是这种幼稚的实现也不需要知道Foo
编写强制转换的编译单元中的知识。
对于此代码:
void func(Abstract1* a1)
{
Abstract2* a2 = dynamic_cast<Abstract2*>(a1);
...
}
你在问:
如果a1
是指向一个Foo
对象,动态转换会返回一个有效的对象指针吗?
答案是肯定的:
a1
为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]:
如果C
是T
指向或引用的类类型,则运行时检查在逻辑上执行如下:
- 如果在 by 指向(引用)的最派生对象中
v
,v
指向(引用)对象的public
基类子C
对象,并且如果只有一个类型的对象从结果指向C
(引用)的子对象派生出来,则指向(引用)v
) 到那个C
对象。否则,如果
v
指向(引用)到最派生对象的公共基类子对象,并且最派生对象的类型具有类型为 的C
明确基类,public
则结果指向(引用)的C
子对象派生最多的对象。否则,运行时检查失败。
通俗地说,我们称之为交叉铸造。
语言没有规定在“当前”翻译单元中知道最派生对象的类型;实现这项工作取决于实现,并且在常见的“虚拟表”模型中,确实如此。