C++对象模型之RTTI的实现原理( 二 )

<< "XX::vfunc()" << endl;}private:int mXX;};使用如下的代码进行测试:
void printTypeInfo(const X *px){cout << "typeid(px) -> " << typeid(px).name() << endl;cout << "typeid(*px) -> " << typeid(*px).name() << endl;}int main(){X x;XX xx;printTypeInfo(printTypeInfo(return 0;}其输出如下:
C++对象模型之RTTI的实现原理文章插图
从输出的结果可以看出 , 无论printTypeInfo函数中指针px指向的对象是基类X的对象 , 还是指向派生类XX的对象 , typeid运行返回的px的类型信息都是相同的 , 因为px为一个静态类型 , 其类型名均为PX1X 。 但是typeid运算符却能正确地计算出了px指向的对象的实际类型 。 (注:由于C++为了保证每一个类在程序中都有一个独一无二的类名 , 所以会对类名通过一定的规则进行改写 , 所以在这里显示的类名跟我们定义的有一些不一样 , 如类XX的类名 , 被改写成了2XX 。 )
那么问题来了 , typeid是如何计算这个类型信息的呢?下面将重点说明这个问题 。
多态类型是通过在类中声明一个或多个virtual函数来区分的 。 因为在C++中 , 一个具备多态性质的类 , 正是内含直接声明或继承而来的virtual函数 。 多态类的对象的类型信息保存在虚函数表的索引的-1的项中 , 该项是一个type_info对象的地址 , 该type_info对象保存着该对象对应的类型信息 , 每个类都对应着一个type_info对象 。 下面就对这一说法进行验证 。
使用如以的代码 , 对上述的类X和类XX的对象的内存布局进行测试:
typedef void (*FuncPtr)();int main(){XX xx;FuncPtr func;char *p = (char*)// 获得虚函数表的地址int **vtbl = (int**)*(int**)p;// 输出虚函数表的地址 , 即vptr的值cout << vtbl << endl;// 获得type_info对象的指针 , 并调用其name成员函数cout << "\t[-1]: " << (vtbl[-1]) << " -> "<< ((type_info*)(vtbl[-1]))->name() << endl;// 调用第一个virtual函数cout << "\t[0]: " << vtbl[0] << " -> ";func = (FuncPtr)vtbl[0];func();// 输出基类的成员变量的值p += sizeof(int**);cout << *(int*)p << endl;// 输出派生类的成员变量的值p += sizeof(int);cout << *(int*)p << endl;return 0;}测试代码 , 对类XX的对象的内存布局进行测试 , 其输出结果如下:
C++对象模型之RTTI的实现原理文章插图
从运行结果可以看到 , 利用虚函数表的-1的项的地址转换成一个type_info的指针类型 , 并调用name成员函数的输出为2XX , 其输出与前面的测试代码中利用typeid的输出一致 。 从而可以知道 , 关于多态类型的计算是通过基类指针或引用指向的对象(子对象)的虚函数表获得的 。
从运行的结果可以知道 , 类XX的对象的内存布局如下:
C++对象模型之RTTI的实现原理文章插图
对于以下的代码片断:
typeid(*px).name()
可能被转换成如下的C++伪代码 , 用于计算实际对象的类型:
(*(type_info*)px->vptr[-1]).name();
在多重继承和虚拟继承的情况下 , 一个类有n(n>1)个虚函数表 , 该类的对象也有n个vptr , 分别指向这些虚函数表 , 但是一个类的所有的虚函数表的索引为-1的项的值(type_info对象的地址)都是相等的 , 即它们都指向同一个type_info对象 , 这样就实现了无论使用了哪一个基类的指针或引用指向其派生类的对象 , 都能通过相应的虚函数表获取到相同的type_info对象 , 从而得到相同的类型信息 。
3)typeid的识别错误的情况
从第2)节可以看到 , typeid对于多态类型是通过虚函数表来计算的 , 若一个基类的指针指向了一个派生类 , 而该派生类并不存在virtual函数会出现什么情况呢?
例如 , 把第2)节中的X和XX类中的virtual函数全部去掉 , 改成以下的代码:
class X{public:X(){mX = 101;}private:int mX;}; class XX : public X{public:XX():X(){mXX = 1001;}private:int mXX;};测试代码不变 , 如下:
void printTypeInfo(const X *px){cout << "typeid(px) -> " << typeid(px).name() << endl;cout << "typeid(*px) -> " << typeid(*px).name() << endl;}int main(){X x;XX xx;printTypeInfo(printTypeInfo( // 注释1return 0;}其输出如下:
C++对象模型之RTTI的实现原理文章插图
从输出的结果可以看到 , 对于注释1的函数调用 , 虽然函数中基类(X)的指针px指向一个派生类对象(XX类的对象xx) , 但是typeid却并不没有像第2)节那样能正确地通过指针px计算出其所指对象的实际类型 。
其原因在于类XX和类X都没有一个virtual函数 , 所以类XX和类X并不表现出多态类的性质 。 所以对类的指针的解引用符合第1)节中所说的静态类型 , 所以其类型信息是在编译时就已经确定的 , 并不需要在程序运行的过程中运行计算 , 所以其输出的类型均为1X而没有输出1XX 。 更进一步说 , 是因为类X和类XX都不存在virtual函数 , 所以类X和XX都不存在虚函数表 , 所以也就没有空间存储跟类X和XX类型有关的type_info对象的地址 。