RTTI(Run-Time Type Identification,运行时类型识别)使程序能够获取由基指针或引用所指向的对象的实际派生类型,即允许“用指向基类的指针或引用来操作对象”的程序能够获取到“这些指针或引用所指对象”的实际派生类型。在C++中,为了支持RTTI提供了两个操作符:dynamic_cast和typeid。
dynamic_cast允许运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转化类型,与之相对应的还有一个非安全的转换操作符static_cast。
typeid类似于sizeof操作符,typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义),typeid表达式形如:typeid(expr);
这里expr是任意表达式或者类型名。如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表达式的静态类型,在编译时就可以确定。标准并没有确切定义type_info,它的确切定义是编译器相关的,但是标准却规定了其实现必须提供如下四种操作:
t1 == t2 如果两个对象t1和t2类型相同,则返回true;否则返回false
t1 != t2 如果两个对象t1和t2类型不同,则返回true;否则返回false
t.name() 返回类型的C-style字符串,类型名字用系统相关的方法产生
t1.before(t2) 返回指出t1是否出现在t2之前的bool值
type_info类提供了public虚析构函数,以使用户能够用其作为基类。它的默认构造函数和拷贝构造函数及赋值操作符都定义为private,所以不能定义或复制type_info类型的对象。程序中创建type_info对象的唯一方法是使用typeid操作符(由此可见,如果把typeid看作函数的话,其应该是type_info的友元)。type_info的name成员函数返回C-style的字符串,用来表示相应的类型名,但务必注意这个返回的类型名与程序中使用的相应类型名并不一定一致(往往如此,见后面的程序),这具体由编译器的实现所决定的,标准只要求实现为每个类型返回唯一的字符串。
#include <iostream>
using namespace std;
class Base {};
class Derived: public Base {};
int main()
{
Base b, *pb;
pb = NULL;
Derived d;
cout << typeid(short).name() << endl
<< typeid(unsigned short).name() << endl
<< typeid(int).name() << endl
<< typeid(unsigned).name() << endl
<< typeid(long).name() << endl
<< typeid(unsigned long).name() << endl
<< typeid(char).name() << endl
<< typeid(unsigned char).name() << endl
<< typeid(float).name() << endl
<< typeid(double).name() << endl
<< typeid(string).name() << endl
<< typeid(Base).name() << endl
<< typeid(b).name()<<endl
<< typeid(pb).name()<<endl
<< typeid(Derived).name() << endl
<< typeid(d).name()<<endl
<< typeid(type_info).name() << endl;
return 0;
}
在linux下输出为:
s
t
i
j
l
m
c
h
f
d
Ss
4Base
4Base
P4Base
7Derived
7Derived
St9type_info
注意:当把typeid作用于指针p的解引用*p时,若指针p为0,则:如果p指向的类型是带虚函数的类类型,则typeid(*p)在运行时抛出一个bad_typeid异常;否则,typeid(*p)的结果与p的值是不相关的,在编译时就可以确定。typeid表达式的这点性质与sizeof表达式相似但又有区别,sizeof一定是在编译时进行计算,也就是说,其只考虑表达式的静态类型,与表达式的动态类型无关(即使有虚函数存在)。
下面对上面的代码稍微添加一点内容,如下:
Base* pb2 = dynamic_cast<Base*>(new Derived);
Base& b2 = d;
Base* pb3 = &d;
cout << typeid(pb2).name() << endl // 输出Base *
<< typeid(b2).name() << endl // 输出Base
<< typeid(pb3).name() << endl // 输出Base *
<< typeid(*pb3).name() << endl; // 输出Base
输出见注释,这是因为Base不包含虚函数,所以typeid的结果指出,表达式的类型是Base或Base*型,尽管他们的底层对象是Derived。即:当typeid操作符的操作数是不带有虚函数的类类型时,typeid操作符会指出操作数的类型,而不是底层对象的类型。
下面为Base类加上一个虚函数,其它保持不变,再看看输出:
class Base
{
public:
virtual ~Base() {}
};
cout << typeid(pb2).name() << endl // 输出Base *
<< typeid(b2).name() << endl // 输出Derived:注意指针与引用的不同
<< typeid(pb3).name() << endl // 输出Base *:注意指针与引用的不同
<< typeid(*pb3).name() << endl; // 输出Derived
这次Base含有虚函数,注意看结果,指针仍然是Base*的,尽管他们指向的是底层对象Derived,而这些Base对象的类型却是Derived的。因为指针pb3不是类类型,所以typeid就返回该指针pb3的指针类型Base *。而*pb3是一个类类型的表达式,而且该类带有虚函数,所以指出该pb3指向的底层对象的类型Derived。如果typeid操作符的操作数是至少包含一个虚函数的类类型时,并且该表达式是一个基类的引用,则typeid操作符指出底层对象的派生类类型。
cout << typeid(7.84).name() << endl // 输出double
<< typeid(Base*).name() << endl // 输出Base*
<< typeid(&pb3).name() << endl; // 输出Base**
在vs2008上,当Base带虚函数时以上代码的输出如下:
short
unsigned short
int
unsigned int
long
unsigned long
char
unsigned char
float
double
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class Base
class Base
class Base *
class Derived
class Derived
class type_info
class Base *
class Derived
class Base *
class Derived
double
class Base *
class Base * *