在分析typeid
之前, 先了解什么是RTTI(运行时类型识别). RTTI使程序能够获取由基指针(引用)所指向的对象的实际派生类型, 允许用指向基类的指针(引用)来操作对象并能够获取到这些指针(引用)所指对象的实际派生类型. c++具体支持typeid
和dynamic_cast
两个操作符.
而typeid
操作符用于返回对象的引用, 实际调用的是type_info
标准库类型, 也在typeinfo
头文件中. 并且typeid
有两种形式, 一种在编译期获得类型, 一种在运行时通过RTTI获取类型.
注意 : typeid
的类中的默认构造函数和复制构造函数都是被删除的, 所以typeid
对象不能执行复制操作, 只能单独创建对象.
运算 | 描述 |
---|---|
t1 == t2 | 如果两个对象t1和t2类型相同,则返回true;否则返回false |
t1 != t2 | 如果两个对象t1和t2类型不同,则返回true;否则返回false |
t.name() | 返回类型的C-style字符串,类型名字由编译器相关的方法产生 |
t1.before(t2) | 返回指出t1是否出现在t2之前的bool值 |
typeid.name()
产生的对象名是由编译器决定的, 所以不同的编译器可能产生的名字并不一样.
typeid
有两种形式, 一种在编译期获得类型, 一种在运行时通过RTTI获取类型.
我们先分析编译期就能获得类型, 再分析RTTI获取类型.
只要不涉及虚表并且没有基类指针(引用)指向子类的情况都是能在编译期间就可以获得类型的信息.
先来看一下在g++编译器中的基本类型名是什么.
cout << typeid(char).name() << endl; // c
cout << typeid(short).name() << endl; // s
cout << typeid(int).name() << endl; // i
cout << typeid(float).name() << endl; // f
cout << typeid(double).name() << endl; // d
添加一个宏定义, 来使不同编译器都能够正常的输出正常的类型.
#include
#define typeid_name(names) abi::__cxa_demangle(typeid(names).name(), 0, 0, NULL)
// 测试
cout << typeid(char).name() << endl; // char
cout << typeid(int).name() << endl; // int
现在就来分析没有虚函数的基类和子类调用时的类型.
#include
#define typeid_name(names) abi::__cxa_demangle(typeid(names).name(), 0, 0, NULL)
class A {};
class B : public A {};
int main()
{
A a, *pa;
B pb;
pa = &pb;
cout << typeid_name(A) << endl; // A
cout << typeid_name(a) << endl; // A
cout << typeid_name(pa) << endl; // A*
cout << typeid_name(*pa) << endl; // A
cout << typeid_name(pb) << endl; // B
exit(EXIT_SUCCESS);
}
上式也并没有需要分析的, 接下来我们再来分析RTTI时typeid
的不同吧
同样使用上面的例子, 但是在基类中添加一个虚函数, 因为有了虚表, 所以在由基类指向子类指针时指针的类型需要在运行时才能确定, 这样也就达到了我们的目的.
#include
#define typeid_name(names) abi::__cxa_demangle(typeid(names).name(), 0, 0, NULL)
class A
{
public:
virtual void fun() {}
};
class B : public A {};
int main()
{
A a, *pa;
B pb;
pa = &pb;
cout << typeid_name(A) << endl; // A
cout << typeid_name(a) << endl; // A
cout << typeid_name(pa) << endl; // A*
cout << typeid_name(*pa) << endl; // B
cout << typeid_name(pb) << endl; // B
exit(EXIT_SUCCESS);
}
有一处发生了改变, typeid_name(*pa)
判断的类型居然是B而不是A, 而typeid_name(pa)
判断的类型又是A*
. 解释如下 :
这里首先要明白pa
和*pa
所代表的对象. pa
它是**A
类型的指针**, 指向B
类型. 所以单单typeid_name(pa)
就是A的指针这是没问题的.
那么现在来解释typeid_name(*pa)
. 先要知道基类有虚函数就代表子类跟基类都会有虚表, 指针只能在运行时确定所指向的是哪一个对象的虚表, 而本例pa
是指向的是B
的虚表, 所以typeid_name(*pa)
推导的结果就是B
.
以上就可以综合为 : 如果typeid操作符的操作数是至少包含一个虚拟函数的类类型时,并且该表达式是一个基类的引用,则typeid操作符指出底层对象的派生类类型.
typeid
需要留意的就是有虚函数存在时, 虚表影响该操作符的类型判断. 其他也就没有什么需要注意的.