第三部分 专题
专业人员的标志体现在他(或她)更加注重精益求精。在本教材的第三部分讨论C++的高级特性,以及那些被C++专业人员中的精英们所使用的开发技术。
在软件研发过程中,有时也许背离正常的面向对象设计的习语常识检查一个对象的运行时类型。大多数情况下需要用虚函数来做这项工作,但是当编写如调试器、数据库观察器或类浏览器这些特殊用途的软件工具时,则需要在运行时来决定它们的类型信息。这就是运行时类型识别(runtime_type identification, RTTI)机制发挥作用的地方。RTTI是第8章的主题。
多重继承的使用在过去的这些年已经达到了滥用的地步,但某些语言甚至不支持它。当适当地运用多重继承时,它对精心制作优雅、高效的程序代码依然是一件强有力的工具。许多涉及多重继承的标准的实际应用在过去的这些年得到了长足的发展,这些内容将在第9章中介绍。
大概自从面向对象技术产生以来,在软件开发中最著名的创新就是设计模式的运用。对于在软件设计中包括的许多共同的问题,设计模式为其描述了解决方案,并且这些解决方案可以应用在许多情形中,并可以用任意一种语言来实现。在第10章中将描述许多精心挑选出的设计模式,并且用C++来实现这些设计模式。
第11章说明多线程编程的优势和所遇到的挑战。虽然大多数操作系统提供了多线程处理的功能,但标准C++现在的版本并没有说明对线程的支持。本教材使用一个可移植的、可免费利用的线程处理库来说明C++程序员可以怎样利用线程的优势去构建更多有用的和应答式的应用程序。
第8章 运行时类型识别
当仅有一个指针或引用指向基类型时,利用运行时类型识别(RTTI)可以找到一个对象的动态类型。
运行时类型识别可能被认为是C++中一个“次要”的特征,当程序员在编程过程中陷入非常困难的境地时,实用主义将会帮助他走出困境。正常情况下,程序员需要有意忽略对象的准确类型,而利用虚函数机制实现那个类型正确操作过程。然而,有时知道一个仅含有一个基类指针的对象的准确的运行时类型(即多半是派生的类型)是非常有用的。有了此信息,就可以更有效地进行某些特殊情况的操作,或者预防基类接口因无此信息而变得笨拙。大部分的类库都包含了虚函数,以便产生足够的运行时类型信息。当在C++中增加了异常处理时,这个特征需要对象的运行时类型的信息,因此,嵌入对这些信息的访问就使下一步工作变得很容易。本章将解释RTTI的用途和如何使用它。
8.1 运行时类型转换
通过指针或引用来决定对象运行时类型的一种方法是使用运行时类型转换(runtime cast),用这种方法可以查证所尝试进行的转换正确与否。当要把基类指针类型转换为派生类型时,这种方法非常有用。由于继承的层次结构的典型描述是基类在派生类之上,所以这种类型转换也称为向下类型转换(downcast)。
请看下面的类层次结构:
在下面的程序代码中,Investment类有一个其他类没有的额外操作,所以能够在运行时知道Security指针是否引用了Investment对象是很重要的。为了实现检查运行时的类型转换,每个类都持有一个整数标识符,以便可以与层次结构中其他的类区别开来。
多态的isA()函数检查其参数是否与它的类型参数(id)相容,就意味着或者id与对象的typeID准确地匹配,或者与对象的祖先之一的类型匹配(因此在这种情况下调用Super:isA())。函数dynacast()在每个类中都是静态的,dynacast()为其指针参数调用isA()来检查类型转换是否有效。如果isA()返回true,则说明类型转换是有效的,并且返回匹配的类型转换指针。否则返回空指针,这告诉调用者类型转换无效,意味着最初的指针没有指向与想要的类型(可转换到的类型)相容的对象。对于能够检查中间类型的类型转换来说,这种机制完全是必须的,例如在前面的程序例子中,从一个指向一个Metal对象的Security类型指针,转换为Investment指针。[1]
在面向对象的应用程序中,因为平常的多态性方案解决了绝大部分问题,对大多数程序来说向下类型转换是不必要的,并且在实际的程序设计中并不提倡。然而,对于像调试器、类浏览器和数据库观察器这些工具程序来说,具有检查多派生类型转换的能力是非常重要的。借助dynamic_cast操作符,C++提供这样一个可检查的类型转换。使用dynamic_cast对前面的程序例子进行重写,就得到下面的程序:
由于原来例子中大部分的代码开销用在了类型转换检查上,所以这个例子就变得如此之短。如同其他新式风格的C++类型转换(static_cast等)一样,dynamic_cast的目标类型放在一对尖括号中,并且转换对象以操作数的方式出现。如果想要安全地进行向下类型转换,dynamic_cast要求使用的目标对象的类型是多态的(polymorphic)。[2]这就要求该类必须至少有一个虚函数。幸运的是,Security基类有一个虚析构函数,所以这里不需要再创建一个额外的函数去做这项工作。因为dynamic_cast在程序运行时使用了虚函数表,所以比起其他新式风格的类型转换操作来说它的代价更高。
用引用而非指针同样也可以使用dynamic_cast,但是由于没有诸如空引用这样的情况,这就需要采用其他方法来了解类型转换是否失败。这个“其他方法”就是捕获bad_cast异常,如下所示:
bad_cast类在<typeinfo>头文件中定义,并且像标准库的大多数的类一样,在std名字空间中声明。
[1]借助微软的编译器,我们必须启用RTTI;在默认情况下这是不能使用的。启用它的命令行选项是/GR。
[2]编译器典型地将一个指向一个类的RTTI表的指针插入它的虚函数表中。