RTTI是运行阶段类型识别(Runtime Type Identification)的简称。RTTI旨在为程序运行阶段确定对象的类型提供一种标准方式。
RTTI的用途
假设有一个类层次结构,其中的类都是从同一个基类派生而来,则可以让基类的指针指向其中任何一个类的对象。这样便可以调用这样的函数:在处理一些信息后,选择一个类,并创建这种类型的对象,然后返回它的地址,而该地址可以被赋给基类指针。但是如何知道指针指向的是哪种对象呢?
在这个问题之前,先要考虑为何要知道类型。
1.可能希望调用类方法的正确版本,在这种情况下,只要该函数是类层次结构中所有成员都拥有的虚函数,则并不真正需要知道对象的类型。
2.派生对象包含不是继承而来的方法,这种情况下,只有某些类型的对象可以使用该方法。
3.处于调试目的,想跟踪生成的对象的类型。
对于后两种情况,RTTI提供解决方案。
RTTI的工作原理
C++有3个支持RTTI的元素:
1.如果可能的话,dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针;否则,该运算符返回0——空指针。
2.typeid运算符返回一个指出对象的类型的值。
3.type_info结构存储了有关特定类型的信息。
注意:只能将RTTI用于包含虚函数的类层次结构,原因在于只有对于这种类层次结构,才应该将派生对象的地址赋给基类指针。
dynamic_cast运算符
dynamic_cast运算符不能知道“指针指向的是哪类对象”,只能知道“是否可以安全的将对象的地址赋给特定类型的指针”,它用于将派生类指针转换为基类指针,其主要用途是确保可以安全的调用虚函数。
class Grand {};
class Superb : public Grand{};
class Magnificent : public Superb{};
Grand * pg = new Grand;
Grand * ps = new Superb;
Grand * pm = new Magnificent;
Magnificent * p1 = (Magnificent *) pm;//安全
Magnificent * p2 = (Magnificent *) ps;//不安全
Superb * p3 = (Magnificent *) pm;//安全
Superb * pm = dynamic_cast<Superb *>(pg);//含义是指针pg的类型是否可被安全的转换为Superb *,如果可以将返回对象的地址,否则返回一个空指针。
通常,如果指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型,则下面的表达式将指针pt转换为Type类型的指针。
dynamic_cast<Type *>(pt)
即使编译器支持RTTI,在默认情况下,它也可能关闭该特性,如果该特性被关闭,程序可能仍能够通过编译,但将出现运行阶段错误。
#include <iostream>
#include <cstdlib>
#include <ctime>
using std::cout;
class Grand
{
int hold;
public:
Grand(int h = 0) : hold(h) {}
virtual void Speak() const {cout << "I am a grand class!\n";}
virtual int Value() const {return hold;}
};
class Superb : public Grand
{
public:
Superb(int h = 0) : Grand(h) {}
void Speak() const {cout << "I am a superb class!!\n";}
virtual void Say() const
{cout << "I hold the superb value of " << Value() << "!\n";}
};
class Magnificent : public Superb
{
char ch;
public:
Magnificent(int h = 0, char c = 'A') : Superb(h), ch(c) {}
void Speak() const {cout << "I am a mangnificent class!!!\n";}
void Say() const {cout << "I hold the character" << ch <<
"and the integer" << Value() << "!\n";}
};
Grand * GetOne();
int main(int argc, char ** argv)
{
std::srand(std::time(0));
Grand * pg;
Superb * ps;
for (int i = 0; i < 5; i++)
{
cout << "i = " << i + 1 << ": ";
pg = GetOne();
pg->Speak();
if (ps = dynamic_cast<Superb *>(pg))//判断pg是否可以安全转换为Superb *
{
ps->Say();
}
}
return 0;
}
Grand * GetOne()
{
Grand * p;
switch (std::rand() % 3)
{
case 0: p = new Grand(std::rand() % 100);
break;
case 1: p = new Superb(std::rand() % 100);
break;
case 2: p = new Magnificent(std::rand() % 100, 'A' + std::rand() % 26);
break;
}
return p;
}
从上面的输出来看,应尽可能的使用虚函数。而只在必要的时候使用RTTI。也可以将dynamic_cast用于引用,
Superb & rs = dynamic_cast<Superb &>(rg);
其用法稍微有点不同:没有与空指针对应的引用值,因此无法使用特殊的引用值来指示失败。当请求不当时,dynamic_cast将引发类型为bad_cast的异常。
2.typeid运算符和type_info类
typeid运算符使得能够确定两个对象是否为同种类型。它与sizeof有些相像,可以接受两种参数:
• 类名;
• 结果为对象的表达式;
typeid运算符返回一个对type_info对象的引用,type_info是在头文件typeinfo中定义的一个类。
typeid(Magnificent) == typeid(*pg);