当前位置: 首页 > 科技观察

C++对象模型RTTI的实现原理

时间:2023-03-12 04:00:31 科技观察

RTTI是RuntimeTypeIdentification的缩写,意思是运行时类型识别。C++引入这种机制是为了让程序在运行时根据基类的指针或引用,获取指针或引用所指向的对象的实际类型。但是现在RTTI的类型识别不仅仅局限于此,它还可以通过typeid运算符来识别所有基本类型(int、pointer等)的变量对应的类型。C++通过以下两个操作提供RTTI:typeid运算符,它返回其表达式或类型名的实际类型。dynamic_cast运算符,它将基类的指针或引用安全地转换为派生类类型的指针或引用。下面分别对这两种操作的实现方式进行详细说明。注意所有测试代码的测试环境为:32位Ubuntu14.04g++4.8.2。如果在不同的环境中进行测试,结果可能会有所不同。1.typeid运算符typeid运算符后跟类型名称或表达式,返回对std::tpeinf类型对象的常量引用。type_info是std中的一个类,用来记录类型相关的信息。类type_info的定义大致如下::type_info(consttype_info&);type_info&operator=(consttype_info&);//数据成员};至于数据成员部分,不同的编译器会有所不同,但最少必须提供的信息是类的真实名称和一些type_info对象的排序算法(通过before()成员函数提供),以及某种形式的描述符,用于表示类的显式类型和该类的任何子类型。从上面的定义也可以看出,type_info提供了两个对象的相等比较操作,但是用户不能自己定义一个type_info对象,只能通过typeid运算符返回一个对象的const引用来使用type_info对象。因为它只声明了一个构造函数(拷贝构造函数),而且是私有的,所以编译器不会合成任何构造函数,赋值运算符也是私有的。这两个操作完全禁止用户定义和复制type_info对象,用户只能通过指向type_info对象的指针或引用来使用类。下面说说typeid的静态类型表达式和动态类型表达式的处理和实现。1)typeid标识静态类型当typeid中的操作数为下列情况之一时,typeid运算符表示操作数的静态类型,即编译时的类型。一个类型名一个基本类型的变量一个具体对象一个没有虚函数的类对象的指针的解引用一个没有虚函数的类对象的引用静态类型在程序运行过程中不会改变,所以没有程序运行时需要计算类型,编译时可以根据操作数的静态类型推导出操作数的类型信息。例如下面的代码片段中,typeid中的操作数都是静态类型:classX{...//有虚函数};classXX:publicX{...//有虚函数};classY{.....//没有虚函数};intmain(){intn=0;XXxx;Yy;Y*py=&y;//int和XX都是类型名cout<"<"<<类型标识(*像素)。name()<"<<((type_info*)(vtbl[-1]))->name()<";func=(FuncPtr)vtbl[0];func();//输出基类成员变量的值p+=sizeof(int**);cout<<*(int*)p<vptr[-1]).name();在多重继承和虚继承的情况下,一个类有n(n>1)个虚函数表,这个类有n个对象vptr分别指向这些虚函数表,但是表项的值(地址type_info对象的)在一个类的所有虚函数表中索引为-1的都是相等的,即它们都指向同一个type_info对象,这样无论用哪个基类指针或引用指向该对象它的派生类,可以通过对应的虚函数表获取相同的type_info对象,从而获得相同的类型信息。3)typeid的错误识别从2)节可以看出,typeid是通过虚函数表为多态类型计算的。如果基类的指针指向派生类,而派生类不存在虚函数会怎样?例如将2)节中X和XX类中的虚函数全部去掉,改成如下代码:classX{public:X(){mX=101;}private:intmX;};classXX:publicX{public:XX():X(){mXX=1001;}private:intmXX;};测试代码不变,如下:voidprintTypeInfo(constX*px){cout<<"typeid(px)->"<"<(px);//转换1cout<(px);//转换2cout<(px);//转换4cout<vptr[-1]静态推导出向下变换的目标类型的type_info信息,即获取类XX的type_info信息比较1)和2)中获取的type_info信息,如果2)中的类型信息等于1)中的类型信息或其基类类型,则返回类的地址相应的对象或子对象,否则返回NULL。引用的情况与指针略有不同,失败时不返回NULL,而是抛出bad_cast异常,因为引用不能引用NULL。

猜你喜欢