8.2 typeid操作符
获得有关一个对象运行时信息的另一个方法,就是用typeid操作符来完成。这种操作符返回一个type_info类的对象,该对象给出与其应用有关的对象类型的信息。如果该对象的类型是多态的,它将给出那个应用(动态类型(dynamic type))的大部分派生类信息;否则,它将给出静态类型信息。typeid操作符的一个用途是获得一个对象的动态类型的名称,例如const char*,就像在下面例子中可以看到的。
这个使用一个特定编译器的程序的输出是:
因为ppb是一个指针,所以输出的第1行是它的静态类型。为了在程序中得到RTTI的结果,需要检查指针或引用目标对象,这在第2行中说明。需要注意的是,RTTI忽略了顶层的const和volatile限定符。借助非多态类型,正好可以获得静态类型(该指针本身的类型)。正如读者所见,这里也支持内置类型。
结果是:因为没有可访问的构造函数并且禁止赋值操作,所以在type_info对象中不能存储typeid操作的结果。必须像在演示中描述的那样来使用它。另外,通过type_info:name()返回的实际字符串依赖于编译器。例如,对于一个名为C的类,某些编译器返回的是字符串“class C”而不是字符串“C”。把typeid应用到解析一个空指针的一个表达式将会引起一个bad_typeid异常被抛出(该异常也定义在<typeinfo>中)。
下面的例子显示由type_info:name()返回那个类名是完全限定的。
因为Nested是One类的一个成员类型,所以结果是One:Nested。
在实现定义的“整理顺序”(对文本的自然排序规则)中,也可以用before(type_info&)询问一个type_info对象是否在另一个type_info对象之前。其返回值为true或false。当编写代码
时,就是询问在当前的整理顺序中,me是否在you之前。如果把type_info对象作为关键字会是很有用处的。
8.2.1 类型转换到中间层次类型
就像读者在前面使用了Security类层次结构的程序中所看到的,dynamic_cast不仅能发现准确的类型,并且能在多层的继承层次结构中将类型转换到中间层类型。下面是另一个例子。
这个例子有关于多重继承的很复杂的情况(在本章后面部分和第9章将会学习到更多有关多重继承的知识)。如果创建一个Mi2对象并将它向上类型转换到该继承层次结构的根(在这种情况下,选择两个可能的根中的一个),可以成功地使dynamic_cast回退至两个派生层MI或Mi2中的任何一个。
甚至可以从一个根到另一个根进行类型转换:
这也是成功的,因为B2实际上指向一个Mi2对象,该Mi2对象含有一个B1类型的子对象。
将类型转换到中间层类型,使dynamic_cast和typeid两者之间产生一个有趣的差异。
typeid操作符始终产生指向静态的type_info型对象的引用,它描述该对象的动态类型。因此,typeid操作符不能给出中间层对象的类型信息。在下面的表达式中(结果是true),像dynamic_cast一样,typeid并没有把b2当做指向派生类的指针:
b2的类型只不过是指针类型:
8.2.2 void型指针
RTTI仅仅为完整的类型工作,这就意味着当使用typeid时,所有的类信息都必须是可利用的。特别是,它不能与void型指针一起工作:
一个void*真实的意思是“无类型信息”。[1]
8.2.3 运用带模板的RTTI
因为所有的类模板所做的工作就是产生类,所以类模板可以很好地与RTTI一起工作。RTTI提供了一条方便的途径来获得对象所在类的名称。下面的示例打印出构造函数和析构函数的调用顺序:
这个模板用一个int常量把一个类和其他类区分开,但是也可使用类型参数。在构造函数和析构函数内部,RTTI信息产生打印的类名。类X利用继承和组合两个方式创建一个类,这个类有一个有趣的构造函数和析构函数的调用顺序。输出如下:
当然,可能会得到不同的输出结果,这取决于编译器如何表示它的name()信息。
[1]dynamic_cast<void*>总是给出完全的对象而不是一个子对象的地址。在第9章中将更详细地解释这一点。