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
从这个类的定义可以发现几个重要的操作:
bool operator==(const type_info& _Other) const noexcept
对比两个类型是否相等
bool operator!=(const type_info& _Other) const noexcept
对比是否不等
const char* name() const noexcept
返回类型的名字
const char* raw_name() const noexcept
返回类型的原始名字
type_info
类的精确定义在不同的编译器实现之间有所不同,但是标准保证这个类定义在 typeinfo 头文件中,并且提供如下函数:这个类提供了一个虚析构函数,因为其将作为一个基类。当编译期希望提供额外的信息,它通常会在
type_info
的派生类中完成。type_info
类没有默认构造函数,拷贝和移动构造函数以及赋值操作符都被定义为被删除的。所以只能通过 typeid 来获得 type_info
对象,而没有别的方式可以得到。type_info
的 name 成员是由编译器决定的,可能不会与程序中使用的名字相匹配,只保证每个类型的名字是唯一的。使用 typeid 操作符
通常使用
typeid
来比较两个表达式的类型或者将一个表达式的类型与特定的类型进行比较:这里
typeid
的参数不是指针本身,如果是指针的话将在编译期返回指针的静态类型。仅当类型具有虚函数时,才需要
typeid
在运行时求得其动态类型,而此时将必须对表达式求值。如果类型没有虚函数,那么typeid
将在编译期返回表达式的静态类型;编译器不需要对表达式求值就可以知道其静态类型。这使得typeid(*p)
如果p
所指向的类型没有虚函数的话,p
可以不是有效的指针(如空指针)。typeid
返回对象类型:- 如果参数为指针,返回就是指针类型
- 如果是解引用指针,那么返回指针指向的对象类型
- 对于继承关系的解引用,需要运行时计算
RTTI
使用 RTTI 的一个场景是定义
equal
函数,如果使用在基类中将equal
函数定义为虚函数,那么其参数将不得不是基类类型,导致函数将无法使用派生类中的特有成员。而且equal
函数应该是类型不同时返回false
。所以equal
函数定义如下: