RTTI目的在于为程序在运行阶段确定对象的类型提供一种标准方式,RTTI只适用于包含虚函数的类。
C++中支持RTTI的三种元素:
- dynamic_cast: 该运算符使用一个指向基类的指针,来生成一个指向派生类的指针。若失败,则返回0(空指针)。
- typeid: 该运算符返回一个指示对象类型的值。
- type_info: 存储包含虚函数的类层次结构,只有存在类层次结构,才可能将派生对象的地址赋给基类指针。
一、dynamic_cast实现RTTI的场景
dynamic_cast运算符是最常用的RTTI组件,它可以确定将某对象地址赋给某特定类型的指针时是否安全。 假设有以下的类层次结构和三种对应指针:
class Grandfather
{
// ...
virtual void func_Gf();
}
class Father : public Grandfather
{
// ...
void func_Gf() override; // 重写Grandfather虚函数
virtual void func_F();
}
class Son : public : Father
{
// ...
void func_Gf() override; // 重写Grandfather虚函数
void func_F() override; // 重写Father虚函数
void func_S();
}
Grandfather *p_Gf = new Grandfather;
Grandfather *p_F = new Father;
Grandfather *p_S = new Son;
此时,对于以下的类型转换,并不全是安全的:
Father *p_1 = (Son *) p_S; // 安全
Son *p_2 = (Son *) p_Gf; // 不安全
Son *p_3 = (Son *) p_S; // 安全
为什么*p_2的类型转换会不安全呢?
原因在于p_Gf指针是基类(Grandfather)对象,将其地址赋给派生类(Son)指针,这就意味着希望基类对象拥有派生类对象的方法。我们知道,派生类对象可能拥有基类对象没有的数据成员,所以这种类型转换是不安全的。
再如p_1指针的类型转换,它将一个派生对象(Son)的地址赋给基类指针(Father);同时,因为p_S是一个Grandfather基类指针,公有继承确保Son对象是一个Father对象也是一个Grandfather对象,它拥有以上三种类共有的方法,因此将派生对象的地址赋给这3种类型指针都是安全的。C++虚函数机制确保了这3种指针指向Son对象时,都将调用Son中重写的方法。
那么,如何确保类型转换的安全性?或者说,如何确定指针指向的对象类型?
当我们知道类型之后,就可知调用特定方法是否安全,不过要调用方法,类型不一定要完全匹配,也可以是基类定义的虚拟方法。一般来说,只有那些指针类型与对象类型(或对象直接基类、间接基类的类型)相同的类型转换才一定安全。
关于上面的例子,使用dynamic_cast:
Father * p = dynamic_cast<Father *>(p_Gf);
// 如果对象(*p_Gf)类型为Father或是从Father直接或间接继承的类,则指针p_Gf将转换为Father类型的指针,否则返回一个空指针。
类Grandfather中定义了func_Gf()虚函数。 类Father重写了直接基类的func_Gf()虚函数,定义了一个新的func_F()虚函数。 类Son重写了间接基类的func_Gf()和直接基类的func_F()虚函数,定义了一个新的func_S()虚函数。
Grandfather *p = new Grandfather;
// or Grandfather *p = new Father;
// or Grandfather *p = new Son;
假如p指针是上面三种类的其中一种创建的,那么当使用指向Grandfather的指针p来调用func_F()或func_S()函数时,调用不一定是正确的。此时就要使用dynamic_cast运算符来检查是否可以转换p为Father或Son指针,如果p是Father或Son类型,则可以安全转换,p也可以安全调用func_F()或func_S()。
if(dynamic_cast<Father*>(p))
p->Func_F();
dynamic_cast还有引用的用法,但转换失败时将引发bad_cast异常,且因为没有与空指针对应的引用值,所以无法使用特殊的引用值来指示失败。
#include <typeinfo>
try
{
Father & rs = dynamic_cast<Father &>(rg);
// ...
}
catch(bad_cast &)
{
// ...
};
二、typeid运算符和type_info类
typeid运算符:
可确定两个对象是否为同一类型,与sizeof类似,typeid接收两种参数:① 类名;② 结果为对象的表达式。
if(typeid(Father) == typeid(*p))
p->func_F();
若p是空指针,将引发bad_typeid异常,该异常类型派生自exception类,声明于头文件typeinfo中。
type_info类:
typeid实际上是返回一个对type_info对象的引用。而type_info是在头文件typeinfo中定义的一个类,其重载了==和!=运算符。
type_info类通常包含一个name()成员,该函数返回一个随实现而异的字符串,通常是类名。
std::cout << typeid(*p).name() << std::endl;
typeid和type_info类成员函数name()都可用于dynamic_cast和virtual函数不能处理的情况。有些编译器对于调用name()可能提供不同的输出。