🥒运行时类型识别
2022-6-10
| 2023-8-2
0  |  阅读时长 0 分钟
type
status
date
slug
summary
tags
category
icon
password
Property

C++提供了运行时类型识别,使得在运行时可以进行类型识别,这个在某些场景很有用途。
 
运行时类型识别(Runtime type identification, RTTI)通过两个操作符提供:
  • typeid 操作符返回给定表达式的类型
  • dynamic_cast 操作符将一个基类指针或者引用转为派生类的指针或引用
 
在有些时候想通过基类指针或引用去调用派生类的函数是不可能的,原因在于无法定义这个虚函数。如果不能使用虚函数,可以使用 RTTI 操作符。另一方面,使用这些操作符将比使用虚成员函数更加易错:程序员必须知道对象的真实类型,并且必须检查转型是否成功。
RTTI 应该被限制使用在有限的范围内,更好的方式是定义虚函数而不是直接管理这些类型。
 

dynamic_cast操作符

dynamic_cast只能用作有虚函数的类型转换(这个可能每个编译器都不同,但是没有虚函数的话dynamic_cast也没啥意义),运算符的调用形式:
e能成功转换为type*类型的情况有三种
  • e的类型是目标type的公有派生类:派生类向基类转换一定会成功。
  • e的类型是目标type的基类,当e是指针指向派生类对象,或者基类引用引用派生类对象时,类型转换才会成功,当e指向基类对象,试图转换为派生类对象时,转换失败。
  • e的类型就是type的类型时,一定会转换成功。
 
如果一条dynamic_cast语句的转换目标是指针类型并且转换失败了,会返回一个空指针,则判断条件为0,即为false;如果转换成功,指针为非空,则判断条件为非零,即true。
引用类型的dynamic_cast和指针类型的dynamic_cast在表示错误发生的方式上略有不同。因为不存在空引用,所以对于引用类型来说无法使用与指针类型完全相同的错误报告策略。当对引用类型转换失败时,程序抛出一个名为std::bad_cast的异常,该异常定义在typeinfo标准库头文件中。
 
指针类型动态转换
如果bp指向一个派生对象,那么dp将会初始化为指向bp所指向的Derived类型的对象的指针,那么使用Derived的操作就是正常的。否则,dp的值将是 0,此时if条件将会失败。
可以在空指针上执行dynamic_cast,其结果是一个空指针。
 
引用类型的动态转换
如果对引用类型进行 dynamic_cast 时,其错误处理方式将会不一样,因为这种方式的转换在无法完成时会抛出异常。
 

typeid操作符

RTTI提供第二个操作符是typeid操作符,其可以知道对象的类型是什么。typeid表达式以typeid(e)的形式存在,其中e是任何表达式或者类型名字,结构是type_info库类或者其公有派生类的一个常量对象引用。type_info类被定义在typeinfo头文件中。
typeid操作符可以被运用于任何表达式,顶层const将会被忽略,如果表达式是引用那么typeid将返回其绑定的对象类型。当表达式是数组或函数时,其不会被转为指针,结果将是数组类型或函数类型。
如果操作数不是类类型或者是一个没有虚函数的类,那么结果是操作数的静态类型。当操作数是一个至少有一个虚函数的类类型左值,那么类型将在运行时被求值。
 
在C++中,提供了一个类,管理这个全局的数据结构,这个数据结构就是type_info
从这个类的定义可以发现几个重要的操作:
  1. bool operator==(const type_info& _Other) const noexcept 对比两个类型是否相等
  1. bool operator!=(const type_info& _Other) const noexcept 对比是否不等
  1. const char* name() const noexcept 返回类型的名字
  1. const char* raw_name() const noexcept 返回类型的原始名字
 
 
type_info 类的精确定义在不同的编译器实现之间有所不同,但是标准保证这个类定义在 typeinfo 头文件中,并且提供如下函数:
notion image
这个类提供了一个虚析构函数,因为其将作为一个基类。当编译期希望提供额外的信息,它通常会在 type_info 的派生类中完成。type_info 类没有默认构造函数,拷贝和移动构造函数以及赋值操作符都被定义为被删除的。所以只能通过 typeid 来获得 type_info 对象,而没有别的方式可以得到。
type_info 的 name 成员是由编译器决定的,可能不会与程序中使用的名字相匹配,只保证每个类型的名字是唯一的。
 
 
使用 typeid 操作符
通常使用typeid来比较两个表达式的类型或者将一个表达式的类型与特定的类型进行比较:
这里typeid的参数不是指针本身,如果是指针的话将在编译期返回指针的静态类型。
仅当类型具有虚函数时,才需要typeid在运行时求得其动态类型,而此时将必须对表达式求值。如果类型没有虚函数,那么typeid将在编译期返回表达式的静态类型;编译器不需要对表达式求值就可以知道其静态类型。这使得typeid(*p)如果p所指向的类型没有虚函数的话,p可以不是有效的指针(如空指针)。
 
typeid返回对象类型:
  1. 如果参数为指针,返回就是指针类型
  1. 如果是解引用指针,那么返回指针指向的对象类型
  1. 对于继承关系的解引用,需要运行时计算
 
 

RTTI

使用 RTTI 的一个场景是定义equal函数,如果使用在基类中将equal函数定义为虚函数,那么其参数将不得不是基类类型,导致函数将无法使用派生类中的特有成员。而且equal函数应该是类型不同时返回false。所以equal函数定义如下:
 
 
  • C++
  • 有关继承的思考特殊类设计
    目录