作者: | 来源:互联网 | 2023-09-24 14:12
RTTI是RuntimeTypeIdentification的缩写,意思是运行时类型识别。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的
RTTI是Runtime Type Identification的缩写,意思是运行时类型识别。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。但是现在RTTI的类型识别已经不限于此了,它还能通过typeid操作符识别出所有的基本类型(int,指针等)的变量对应的类型。
C++通过以下的两个操作提供RTTI:
(1)typeid运算符,该运算符返回其表达式或类型名的实际类型。
(2)dynamic_cast运算符,该运算符将基类的指针或引用安全地转换为派生类类型的指针或引用。
下面分别详细地说明这两个操作的实现方式。
注所有的测试代码的测试环境均为:32位Ubuntu 14.04 g++ 4.8.2,若在不同的环境中进行测试,结果可能有不同。
1、typeid运算符
typeid运算符,后接一个类型名或一个表达式,该运算符返回一个类型为std::tpeinf的对象的const引用。type_info是std中的一个类,它用于记录与类型相关的信息。类type_info的定义大概如下:
class type_info
{
public:
virtual ~type_info();
bool operator==(const type_info&)const;
bool operator!=(const type_info&)const;
bool before(const type_info&)const;
const char* name()const;
private:
type_info(const type_info&);
type_info& operator=(const type_info&);
// data members
};
至于data members部分,不同的编译器会有所不同,但是都必须提供最小量的信息是class的真实名称和在type_info对象之间的某些排序算法(通过before()成员函数提供),以及某些形式的描述器,用来表示显式的类的类型和该类的任何子类型。
从上面的定义也可以看到,type_info提供了两个对象的相等比较操作,但是用户并不能自己定义一个type_info的对象,而只能通过typeid运算符返回一个对象的const引用来使用type_info的对象。因为其只声明了一个构造函数(复制构造函数)且为private,所以编译器不会合成任何的构造函数,而且赋值操作运行符也为private。这两个操作就完全禁止了用户对type_info对象的定义和复制操作,用户只能通过指向type_info的对象的指针或引用来使用该类。
下面说说,typeid对静态类型的表达式和动态类型的表达式的处理和实现。
1)typeid识别静态类型
当typeid中的操作数是如下情况之一时,typeid运算符指出操作数的静态类型,即编译时的类型。
(1)类型名
(2)一个基本类型的变量
(3)一个具体的对象
(4)一个指向不含有virtual函数的类对象的指针的解引用
(5)一个指向不含有virtual函数的类对象的引用
静态类型在程序的运行过程中并不会改变,所以并不需要在程序运行时计算类型,在编译时就能根据操作数的静态类型,推导出其类型信息。例如如下的代码片断,typeid中的操作数均为静态类型:
class X { ...... // 具有virtual函数 };
class XX : public X { ...... // 具有virtual函数};
class Y { ...... // 没有virtual函数};
int main()
{
int n = 0;
XX xx;
Y y;
Y *py = &y;
// int和XX都是类型名
cout <
2)typeid识别多态类型
当typeid中的操作数是如下情况之一时,typeid运算符需要在程序运行时计算类型,因为其其操作数的类型在编译时期是不能被确定的。
(1)一个指向不含有virtual函数的类对象的指针的解引用
(2)一个指向不含有virtual函数的类对象的引用
多态的类型是可以在运行过程中被改变的,例如,一个基类的指针,在程序运行的过程中,它可以指向一个基类对象,也可以指向该基类的派生类的对象,而typeid运算符需要在运行过程中识别出该基类指针所指向的对象的实际类型,这就需要typeid运算符在运行过程中计算其指向的对象的实际类型。例如对于以下的类定义:
class X
{
public:
X()
{
mX = 101;
}
virtual void vfunc()
{
cout <<"X::vfunc()" <
使用如下的代码进行测试:
void printTypeInfo(const X *px)
{
cout <<"typeid(px) -> " < " <
其输出如下:
运行结果分析
px是一个基类(X)的指针,但是它指向了派生类XX的一个对象。在转换1中,转换成功,因为px指向的对象确实为XX的对象。在转换2中,转换失败,因为px指向的对象并不是一个YX对象,此时dymanic_cast返回NULL。转换3为C风格的类型转换而转换4使用的是C++中的静态类型转换,它们均能成功转换,但是这个对象实际上并不是一个YX的对象,所以在转换3和转换4中,若继续通过指针使用该对象必然会导致错误,所以这个转换是不安全的。
从上述的结果可以看出在向下转型中,只有dynamic_case才能实现安全的向下转型。那么dynamic_case是如何实现的呢?有了上面typeid和虚函数表的知识后,这个问题并不难解释了,以转换1为例。
1)计算指针或引用变量所指的对象的虚函数表的type_info信息,如下:
*(type_info*)px->vptr[-1]
2)静态推导向下转型的目标类型的type_info信息,即获取类XX的type_info信息
3)比较1)和2)中获取到的type_info信息,若2)中的类型信息与1)中的类型信息相等或是其基类类型,则返回相应的对象或子对象的地址,否则返回NULL。
引用的情况与指针稍有不同,失败时并不是返回NULL,而是抛出一个bad_cast异常,因为引用不能参考NULL。
C++对象模型之RTTI的实现原理