本章讨论 C++ 标准中较新的强制类型转换运算符: const_cast、reinterpret_cast、static_cast 和 dynamic_cast。强制类型转换可以将对象或值从一种类型强制转换为另一种类型。
这些强制类型转换操作比以前的强制类型转换操作更好控制。dynamic_cast<> 运算符提供了一种检查指向多态类的指针的实际类型的方法。可以用文本编辑器搜索所有新式强制类型转换(搜索 _cast),而查找旧式强制类型转换需要进行语法分析。
否则,新的强制类型转换全部执行传统强制类型转换符号允许的强制类型转换子集。例如,const_cast<int*>(v) 可以写为 (int*)v。新的强制类型转换仅将各种可用的操作分类以更清楚地表示您的意图,并允许编译器提供更完善的检查。
强制类型转换运算符是始终启用的。强制类型转换符不能被禁用。
可以使用表达式 const_cast<T>(v) 更改指针或引用的 const 或 volatile 限定符。(在新式强制类型转换中,只有 const_cast<> 可以删除 const 限定符。)T 必须是指针、引用或指向成员的指针类型。
class A { public: virtual void f(); int i; }; extern const volatile int* cvip; extern int* ip; void use_of_const_cast() { const A a1; const_cast<A&>(a1).f(); // remove const ip = const_cast<int*> (cvip); // remove const and volatile } |
表达式 reinterpret_cast<T>(v) 用于更改对表达式 v 值的解释。该表达式可用于在指针和整型之间,在不相关的指针类型之间,在指向成员的指针类型之间,和在指向函数的指针类型之间转换类型。
使用 reinterpret_cast 运算符可能会得到未定义的结果或实现相关结果。以下几点描述了唯一确定的行为:
指向数据对象或函数的指针(但不是指向成员的指针)可以转换为足够包含该指针的任何整型。(long 类型总是足以包含 C++ 编译器支持的体系结构上的指针值。)转换回原始类型时,指针值将与原始指针值相比较。
指向(非成员)函数的指针可以转换为指向不同(非成员)函数类型的指针。如果转换回原始类型,指针值将与原始指针相比较。
假设新类型的对齐要求没有原始类型严格,则指向对象的指针可以转换为指向不同对象类型的指针。转换回原始类型时,指针值将与原始指针值相比较。
如果可以使用重新解释强制类型转换将“指向 T1 的指针”类型的表达式转换为“指向 T2 的指针”类型的表达式,则 T1 类型左值可以转换为“对 T2 的引用”类型。
如果 T1 和 T2 都是函数类型或都是对象类型,则“指向 T1 类型的 X 的成员的指针”类型右值可以显式转换为“指向 T2 类型的 Y 的成员的指针”类型右值。
在所有允许的情况下,空指针类型转换为不同的空指针类型后仍然是空指针。
reinterpret_cast 运算符不能用来删除 const,可使用 const_cast 来实现。
reinterpret_cast 运算符不能用来在指向同一类分层结构中不同类的指针之间进行转换,可使用静态或动态强制类型转换来实现。(reinterpret_cast 不执行所需的调整。)这一点在以下示例中描述:
class A {int a; public: A();}; class B: public A {int b, c;}; void use_of_reinterpret_cast() { A a1; long l = reinterpret_cast<long>(&a1); A* ap = reinterpret_cast<A*>(l); // safe B* bp = reinterpret_cast<B*>(&a1); // unsafe const A a2; ap = reinterpret_cast<A*>(&a2); // error, const removed } |
表达式 static_cast<T>(v) 用于将表达式 v 的值转换为 T 类型。该表达式可用于任何隐式允许的转换类型。此外,任何值的类型都可以强制转换为 void,并且,如果强制类型转换与旧式强制类型转换一样合法,则任何隐式转换都可以反向执行。
class B {...}; class C: public B {...}; enum E {first=1, second=2, third=3}; void use_of_static_cast(C* c1) { B* bp = c1; // implicit conversion C* c2 = static_cast<C*>(bp); // reverse implicit conversion int i = second; // implicit conversion E e = static_cast<E>(i); // reverse implicit conversion } |
static_cast 运算符不能用于删除 const。可以使用 static_cast 对分层结构“向下”强制类型转换(从基到派生的指针或引用),但是不会检查转换,因此结果可能无法使用。static_cast 不能用于从虚拟基类向下强制类型转换。
指向类的指针(或引用)可以实际指向(引用)从该类派生的任何类。有时希望指向完全派生类的指针,或指向完整对象的某些其他子对象。动态强制类型转换可以实现这些功能。
在兼容模式 (-compat[=4]) 下编译时,如果程序使用动态强制类型转换,则必须使用 -f eatures=rtti 进行编译。
动态强制类型转换可将指向一个类 T1 的指针(或引用)转换到指向另一个类 T2 的指针(或引用)。T1 和 T2 必须属于同一分层结构,类必须是可访问的(通过公共派生),并且转换必须明确。此外,除非转换是从派生类到其一个基类,否则,包括 T1 和 T2 的分层结构的最小部分必须是多态的(至少有一个虚函数)。
在表达式 dynamic_cast<T>(v) 中,v 是要进行强制类型转换的表达式,T 是要转换成的类型。T 必须是指向完整类的类型(其定义可见)的指针或引用,或指向 cv void 的指针,其中 cv 是空字符串、const、volatile 或 const volatile。
对分层结构进行向上强制类型转换时,如果 T 指向(或引用)v 所指向(引用)类型的基类,则该转换等效于 static_cast<T>(v)。
如果 T 是 void*,则结果是指向完整对象的指针。也就是说,v 可能指向某完整对象的其中一个基类。在这种情况下,dynamic_cast<void*>(v) 的结果如同将 v 沿分层结构向下转换为完整对象(无论什么对象)的类型,然后转换为 void*。
强制类型转换到 void* 时,分层结构必须是多态的(有虚函数)。
向下或交叉强制类型转换分层结构时,分层结构必须是多态的(具有虚函数)。结果在运行时检查。
对分层结构向下或交叉强制类型转换时,有时不能从 v 转换到 T。例如,尝试的转换可能不明确,T 可能无法访问,或 v 可能未指向(或引用)必要类型的对象。如果运行时检查失败且 T 是指针类型,则强制类型转换表达式的值是 T 类型的空指针。如果 T 是引用类型,不会返回任何值(没有 C++ 中的空引用),并且会抛出标准异常 std::bad_cast。
例如,此公共派生的示例成功:
#include <assert.h> #include <stddef.h> // for NULL class A {public: virtual void f();}; class B {public: virtual void g();}; class AB: public virtual A, public B {}; void simple_dynamic_casts() { AB ab; B* bp = &ab; // no casts needed A* ap = &ab; AB& abr = dynamic_cast<AB&>(*bp); // succeeds ap = dynamic_cast<A*>(bp); assert(ap!= NULL); bp = dynamic_cast<B*>(ap); assert(bp!= NULL); ap = dynamic_cast<A*>(&abr); assert(ap!= NULL); bp = dynamic_cast<B*>(&abr); assert(bp!= NULL); } |
但此示例失败,因为基类 B 不可访问。
#include <assert.h> #include <stddef.h> // for NULL #include <typeinfo> class A {public: virtual void f() {}}; class B {public: virtual void g() {}}; class AB: public virtual A, private B {}; void attempted_casts() { AB ab; B* bp = (B*)&ab; // C-style cast needed to break protection A* ap = dynamic_cast<A*>(bp); // fails, B is inaccessible assert(ap == NULL); try { AB& abr = dynamic_cast<AB&>(*bp); // fails, B is inaccessible } catch(const std::bad_cast&) { return; // failed reference cast caught here } assert(0); // should not get here } |
如果在一个单独的基类中存在虚拟继承和多重继承,那么实际动态强制类型转换必须能够识别出唯一的匹配。如果匹配不唯一,则强制类型转换失败。例如,假定有如下附加类定义:
class AB_B: public AB, public B {}; class AB_B__AB: public AB_B, public AB {}; |
示例:
void complex_dynamic_casts() { AB_B__AB ab_b__ab; A*ap = &ab_b__ab; // okay: finds unique A statically AB*abp = dynamic_cast<AB*>(ap); // fails: ambiguous assert(abp == NULL); // STATIC ERROR: AB_B* ab_bp = (AB_B*)ap; // not a dynamic cast AB_B*ab_bp = dynamic_cast<AB_B*>(ap); // dynamic one is okay assert(ab_bp!= NULL); } |
dynamic_cast 返回的空指针错误可用作两个代码体之间的条件,一个用于类型确定正确时处理强制类型转换,另一个用于类型确定错误时处理强制类型转换。
void using_dynamic_cast(A* ap) { if (AB *abp = dynamic_cast<AB*>(ap)) { // abp is non-null, // so ap was a pointer to an AB object // go ahead and use abp process_AB(abp);} else { // abp is null, // so ap was NOT a pointer to an AB object // do not use abp process_not_AB(ap); } } |
在兼容模式 (-compat[=4]) 下,如果尚未使用 -features=rtti 编译器选项启用运行时类型信息,则编译器将 dynamic_cast 转换到 static_cast 并发出警告。
如果禁用了异常,编译器会将 dynamic_cast<T&> 转换为 static_cast<T&> 并发出警告。(对引用类型的 dynamic_cast 要求在运行时发现转换无效的情况下抛出异常。)有关异常的信息,请参见7.5.3 诊断有问题的搜索。
动态强制类型转换需要比对应的设计模式慢,例如虚函数的转换。请参见由 Erich Gamma 编著的《Design Patterns: Elements of Reusable Object-Oriented Software》(Addison-Wesley 出版,1994)。