15.13 向下类型转换
我们可能猜测,既然存在向上类型转换—在类层次中向上移动,那也应该存在可以向下移动的向下类型转换(downcasting)。但是由于在一个继承层次上向上移动时,类总是集中于更一般的类,因此向上类型转换是容易的。这就是说,当进行向上类型转换时,总是清楚地派生自祖先类(典型地总是一个,除了多重继承的情况),而当向下类型转换时,通常会有多种选择让我们进行类型转换。更特殊些,Circle是Shape的一种类型(这是向上类型转换),但如果对一个Shape进行向下类型转换,它可能会是Circle、Square、Triangle等。因此,对于安全地进行向下类型转换,就出现了两难的选择。(但更重要的是,要问问自已,为什么首先使用向下类型转换而不用多态性来自动地获取正确的类型。在本书的第2章中介绍了向下类型转换的避免。)
C++提供了一个特殊的称为dynamic_cast的显式类型转换(explicit cast)(在第3章中介绍过),它就是一种安全类型向下类型转换(type-safe downcast)的操作。当使用dynamic_cast来试着向下类型转换一个特定的类型,仅当类型转换是正确的并且是成功的时,返回值会是一个指向所需类型的指针,否则它将返回0来表示这并不是正确的类型。下面有一个小例子。
当使用dynamic_cast时,必须对一个真正多态的层次进行操作—它含有虚函数—这因为dynamic_cast使用了存储在VTABLE中的信息来判断实际的类型。这里,基类含有一个虚析构函数,这就足够了。main()中,一个Cat指针被向上类型转换到Pet,然后又试着向下类型转换到一个Dog指针和一个Cat指针。运行这个程序时,打印出这两个指针,可以看到不正确的向下类型转换返回了0值。当然,无论何时进行向下类型转换,我们都有责任进行检验以确保类型转换的返回值为非0。但我们不用确保指针要完全一样,这是因为通常在向上类型转换和向下类型转换时指针会进行调整(特别是在多重继承的情况下)。
dynamic_cast运行时需要一点额外的开销;不多,但如果执行大量的dynamic_cast(这时我们的程序设计就有严重的问题),就会影响性能。有时,在进行向下类型转换时,我们可以知道正在处理的是何种类型,这时使用dynamic_cast产生的额外开销就没有必要,可以通过使用static_cast来代替它。
在这个程序中,使用了一个新的特征,本书第2卷会有一章完全介绍这一主题:C++的运行时类型识别(Run-time_type_information, RTTI)机制。RTTI允许我们得到在进行向上类型转换时丢失的类型信息。dynamic_cast实际上就是RTTI的一种形式。这里,typeid关键字(在头文件<typeinfo>中声明)用来检测指针的类型。可以看到,向上类型转换的Shape指针的类型相继与Circle指针和Square指针相比较,来判断它们是否匹配。RTTI的内容远远不止typeid,我们也可以想象它能通过虚函数简单合理地实现我们自已的类型信息系统。
程序创建了一个Circle对象,它的地址被向上类型转换为Shape指针;第二个表达式显示了我们如何使用static_cast来进行更加显式地向上类型转换。然而,由于向上类型转换总是安全的并且是通用的,因此我认为用一个显式类型转换来进行向上类型转换将是混乱和没有必要的。
RTTI用于判定类型,static_cast用于执行向下类型转换。但要注意,在这个设计中,处理效率同使用dynamic_cast是一样的,并且客户程序员必须进行检测来发现那些实际成功的类型转换。我们希望在不使用dynamic_cast而使用static_cast之前,有一个比上面例子更加确定的环境(并且在使用dynamic_cast之前,我们希望可以再一次仔细地检查我们的设计)。
如果类层次中没有虚函数(这是一个有问题的设计),或者如果有其他的需要,要求我们安全地进行向下类型转换,与使用dynamic_cast相比,静态地执行向下类型转换会稍微快一点。另外,static_cast不允许类型转换到该类层次的外面,而传统的类型转换是允许的,所以它们会更安全。但是静态地浏览类层次总是有风险的,所以除非特殊情况,我们一般使用dynamic_cast。