目录
1. dynamic_cast 语法格式
dynamic_cast < type-id > ( expression )
dynamic_cast 运算符接受两个操作数:一个用 < 和 > 括起来的类型 type-id (类指针、引用、或void*),以及一个用 ( 和 ) 括起来的指针或引用。dynamic_cast 不允许意外违反私有和受保护基类的保护(译注:这是与C语言风格的强制转换的主要区别,因此涉及强制类对象指针转换时推荐这种方法)。由于用作向上转型(即子类向基类的转换)的 dynamic_cast 与简单赋值完全相同,因此它不涉及任何开销,并且对其词法上下文很敏感。
dynamic_cast 的目的是处理编译器无法确定转换正确性的情况。dynamic_cast 需要一个指向多态类型的指针或引用(即当向下转换时(基类向派生类转换), expression 必须是多态的引用或指针),以便进行向下转换或交叉转换。要求指针的类型具有多态性简化了 dynamic_cast 的实现,因为它可以轻松找到保存有关对象类型的必要信息的位置。因此,当向下转换时 ,dynamic_cast 要求的指针或者引用必须具有 virtual 基函数。
即,当向上转换时,等同于赋值;当向下转换时,派生类必须是多态的,即基类必须有virtual函数。
2. dynamic_cast 用法示例
2.1 向上转换
如果 type-id 是expression(表达式)的一个直接或间接基类的无歧义指针,则指向类型 type-id 的唯一子对象(即基类对象)的子针就是操作的结果。例如:
C++:
// dynamic_cast_1.cpp
// compile with: /c
class B { };
class C : public B { };
class D : public C { };
void f(D* pd) {
C* pc = dynamic_cast<C*>(pd); // ok: C 是直接基类
// pc 指向pd的 C 子对象
B* pb = dynamic_cast<B*>(pd); // ok: B 是D的间接基类
// pb 指向pd的 B 指对象
}
这种类型称为“向上(基类)转换”,因为它在类层级结构中将一个指针从派生类向基类移动(而类层级结构基类在上),向上转换是隐式转换。
2.2 void* 转换
如果 type-id 是void*,则系统通过运行时检查去确定 expression(表达式)的实际类型。其结果是指向expression所指向的完整对象的指针(而不是子对象)。
C++:
// dynamic_cast_2.cpp
// compile with: /c /GR
class A { virtual void f(){};};
class B { virtual void f(){};};
void f() {
A* pa = new A;
B* pb = new B;
void* pv = dynamic_cast<void*>(pa);
// pv 此时指向类型A的一个对象
pv = dynamic_cast<void*>(pb);
// pv 此时指向类型B的一个对象
}
如果 type-id 不是 void*,则会进行运行时检查,查看expression指向的对象是否可以转换为 type-id 指向的类型。
2.3 向下转换
如果 expression 的类型是type-id类型的基类,则会进行运行时检查,查看是否expression实际上指向type-id类型的完整对象。若成立,结果是指向type-id类型的完整对象的指针。例如:
C++:
// dynamic_cast_3.cpp
// compile with: /c /GR
class B { virtual void f(){};};
class D : public B { virtual void f(){};};
void f() {
B* pb = new D; // 不明显但尚可
B* pb2 = new B;
D* pd = dynamic_cast<D*>(pb); // ok: pb 实际上指向 D
D* pd2 = dynamic_cast<D*>(pb2); // pb2 指向 B 而非 D,因此结果为 null
}
这种类型的转换称为“向下转换”,因为它在类层级结构上将指针从基类向派生类移动。
2.4 多继承下的转换
在多继续的情况下,有可能会引入歧义。考虑如上图中的类层级结构。
指向类型D 的对象的指针,可以安全地转化为B或C。然而,若 D 转化为指向A的对象,则结果是A的哪个对象(即走哪条路径转换呢)?这会导致抛出一个表示歧义错误。为了避开这个错误,可以执行两个无歧义转换。例如:
C++:
// dynamic_cast_4.cpp
// compile with: /c /GR
class A { virtual void f(){};};
class B : public A { virtual void f(){};};
class C : public A { virtual void f(){};};
class D : public B, public C { virtual void f(){};};
void f() {
D* pd = new D;
A* pa = dynamic_cast<A*>(pd); // C4540, 运行时歧义转换失败
// 返回 nullptr
B* pb = dynamic_cast<B*>(pd); // 先转换为 B
A* pa2 = dynamic_cast<A*>(pb); // ok: 无歧义转换
}
当使用virtual 基类的时候,会引入更多的歧义。考虑下面图表给出的类层级结构。
------------------表示virtual基类的类层级结构------------------------
在这个图表示的类层级结构中,A 是一个 virtual基类。已知类 E的一个实例以及一个指向A子对象的指针,一个向B 的指针dynamic_cast转换会因为歧义而失败。必须先转换回完整的 E 对象,然后按照非歧义的转换方法按类层级结构向上转换,直到转换为正确的B对象。
考虑如下所示的类层级结构图。
------------------表示重复基类的类层级结构(E是多重继续的基类)-----------------
已知一个类型E的对象,以及一个指向 D子对象的一个指针,要从 D 子对象导航到最左边的 A 子对象,可以进行三次转换。您可以执行从 D 指针到 E 指针的 dynamic_cast 转换,然后执行从 E 到 B 的转换(dynamic_cast 或隐式转换),最后执行从 B 到 A 的隐式转换。例如:
C++:
// dynamic_cast_5.cpp
// compile with: /c /GR
class A { virtual void f(){};};
class B : public A { virtual void f(){};};
class C : public A { };
class D { virtual void f(){};};
class E : public B, public C, public D { virtual void f(){};};
void f(D* pd) {
E* pe = dynamic_cast<E*>(pd);
B* pb = pe; // 向上转换, 隐式转换
A* pa = pb; // 向上转换,隐式转换
}
dynamic_cast运算符也可用于执行“交叉转换”。使用同样的类层级结构,可以将指针从 B 子对象转换为 D 子对象,只要完整对象是 E 类型即可。
考虑到交叉转换,只需两步就可以将指向 D 的指针转换为指向最左侧子对象 A 的指针。您可以执行从 D 到 B 的交叉转换,然后执行从 B 到 A 的隐式转换。例如:
C++:
// dynamic_cast_6.cpp
// compile with: /c /GR
class A { virtual void f(){};};
class B : public A { virtual void f(){};};
class C : public A { };
class D { virtual void f(){};};
class E : public B, public C, public D { virtual void f(){};};
void f(D* pd) {
B* pb = dynamic_cast<B*>(pd); // 交叉转换
A* pa = pb; // 向上转换,隐式转换
}
注: pb的值为null 。
空指针值通过 dynamic_cast 转换为目标类型的空指针值。
使用 dynamic_cast < type-id > ( expression ) 时,如果无法将 expression 安全地转换为类型 type-id,则运行时检查会导致转换失败。例如:
C++:
// dynamic_cast_7.cpp
// compile with: /c /GR
class A { virtual void f();};
class B { virtual void f();};
void f() {
A* pa = new A;
B* pb = dynamic_cast<B*>(pa); // 运行时转换失败, 不安全;
// B 不从A派生
}
转换为指针类型失败时,其值为空指针。转换为引用类型失败时,将引发 bad_cast 异常。如果 expression 未指向或引用有效对象,则会引发 __non_rtti_object 异常。