您的位置:首页 > 其它

RTTI运行时类型识别(转)

2007-11-19 00:37 351 查看
运行时类型信息机制包括3个主要部分:
1、运算符dynamic_cast,给它一个指向某对象的基类指针,它能得到一个到这个对象的派生类指针。只有在被指对象确实属于所指明的派生类时,运算符dynamic_cast才给出这个指针,否则就返回0。
2、运算符typeid,它给一个给定的基类指针识别出被指对象的确切类型。
3、结构type_info ,作为与有关类型的更多运行时类型信息的挂接点(hook)。

模板、异常、运行时类型信息和名字空间是C++对C的主要扩充.

--------------------------------------------------------------------------------------------

Run-time type information (RTTI) is a mechanism that allows the type of an object to be determined during program execution. RTTI was added to the C++ language because many vendors of class libraries were implementing this functionality themselves. This caused incompatibilities between libraries. Thus, it became obvious that support for run-time type information was needed at the language level.

RTTI as known Runtime Type Identification.
If you want to cast the object of one class as the object of another class so u require to know the type of the object at the runtime and u may want to cofirm is tht object of the same class you want to change we use the typeid operator to check what is the objects type and then use dynamic cast to cast the object.


---------------------------------------------------------------------------------------------

RTTI 是“Runtime Type Information”的缩写,意思是:运行时类型信息。它提供了运行时确定对象类型的方法。本文将简略介绍 RTTI 的一些背景知识、描述 RTTI 的概念,并通过具体例子和代码介绍什么时候使用以及如何使用 RTTI;本文还将详细描述两个重要的 RTTI 运算符的使用方法,它们是 typeid 和 dynamic_cast。
其实,RTTI 在C++中并不是什么新的东西,它早在十多年以前就已经出现了。但是大多数开发人员,包括许多高层次的C++程序员对它并不怎么熟悉,更不用说使用 RTTI 来设计和编写应用程序了。
一些面向对象专家在传播自己的设计理念时,大多都主张在设计和开发中明智地使用虚拟成员函数,而不用 RTTI 机制。但是,在很多情况下,虚拟函数无法克服本身的局限。每每涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。如何确定对象的动态类型呢?答案是使用内建的 RTTI 中的运算符:typeid 和 dynamic_cast。
首先让我们来设计一个类层次,假设我们创建了某个处理文件的抽象基类。它声明下列纯虚拟函数:open()、close()、read()和 write():

class File
{
public:
virtual int open(const string & filename)=0;
virtual int close(const string & filename)=0;
//
virtual ~File()=0; // 记住添加纯虚拟析构函数(dtor)
};

现在从 File 类派生的类要实现基类的纯虚拟函数,同时还要提供一些其他的操作。假设派生类为 DiskFile,除了实现基类的纯虚拟函数外,还要实现自己的flush()和defragment()操作:

class DiskFile: public File
{
public:
int open(const string & filename);

// 实现其他的纯虚拟函数
......

// 自己的专有操作
virtual int flush();
virtual int defragment();
};

接着,又从 DiskFile 类派生两个类,假设为 TextFile 和 MediaFile。前者针对文本文件,后者针对音频和视频文件:

class TextFile: public DiskFile
{
// ......
int  sort_by_words();
};

class MediaFile: public DiskFile
{
//......
};

我们之所以要创建这样的类层次,是因为这样做以后可以创建多态对象,如:

File *pfile; // *pfile的静态类型是 File
if(some_condition)
pfile = new TextFile; // 动态类型是 TextFile
else
pfile = new DiskFile; // 动态类型是 DiskFile

假设你正在开发一个基于图形用户界面(GUI)的文件管理器,每个文件都可以以图标方式显示。当鼠标移到图标上并单击右键时,文件管理器打开一个菜单,每个文件除了共同的菜单项,不同的文件类型还有不同的菜单项。如:共同的菜单项有“打开”“拷贝”、和“粘贴”,此外,还有一些针对特殊文件的专门操作。比如,文本文件会有“编辑”操作,而多媒体文件则会有“播放”菜单。为了使用 RTTI 来动态定制菜单,文件管理器必须侦测每个文件的动态类型。利用 运算符 typeid 可以获取与某个对象关联的运行时类型信息。typeid 有一个参数,传递对象或类型名。因此,为了确定 x 的动态类型是不是Y,可以用表达式:typeid(x) == typeid(Y)实现:

#include <typeinfo> // typeid 需要的头文件
void menu::build(const File * pfile)
{
if (typeid(*pfile)==typeid(TextFile))
{
add_option("edit");
}
else if (typeid(*pfile)==typeid(MediaFile))
{
add_option("play");
}
}

使用 typeid 要注意一个问题,那就是某些编译器(如 Visual C++)默认状态是禁用 RTTI 的,目的是消除性能上的开销。如果你的程序确实使用了 RTTI,一定要记住在编译前启用 RTTI。使用 typeid 可能产生一些将来的维护问题。假设你决定扩展上述的类层次,从MediaFile 派生另一个叫 LocalizeMedia 的类,用这个类表示带有不同语言说明文字的媒体文件。但 LocalizeMedia 本质上还是个 MediaFile 类型的文件。因此,当用户在该类文件图标上单击右键时,文件管理器必须提供一个“播放”菜单。可惜 build()成员函数会调用失败,原因是你没有检查这种特定的文件类型。为了解决这个问题,你必须象下面这样对 build() 打补丁:

void menu::build(const File * pfile)
{

//......

else if (typeid(*pfile)==typeid(LocalizedMedia))
{
add_option("play");
}
}

唉,这种做法真是显得太业余了,以后每次添加新的类,毫无疑问都必须打类似的补丁。显然,这不是一个理想的解决方案。这个时候我们就要用到 dynamic_cast,这个运算符用于多态编程中保证在运行时发生正确的转换(即编译器无法验证是否发生正确的转换)。用它来确定某个对象是 MediaFile 对象还是它的派生类对象。dynamic_cast 常用于从多态编程基类指针向派生类指针的向下类型转换。它有两个参数:一个是类型名;另一个是多态对象的指针或引用。其功能是在运行时将对象强制转换为目标类型并返回布尔型结果。也就是说,如果该函数成功地并且是动态的将 *pfile 强制转换为 MediaFile,那么 pfile的动态类型是 MediaFile 或者是它的派生类。否则,pfile 则为其它的类型:

void menu::build(const File * pfile)
{
if (dynamic_cast <MediaFile *> (pfile))
{
// pfile 是 MediaFile 或者是MediaFile的派生类 LocalizedMedia
add_option("play");
}
else if (dynamic_cast <TextFile*> (pfile))
{
// pfile 是 TextFile 是TextFile的派生类
add_option("edit");
}
}

细细想一下,虽然使用 dynamic_cast 确实很好地解决了我们的问题,但也需要我们付出代价,那就是与 typeid 相比,dynamic_cast 不是一个常量时间的操作。为了确定是否能完成强制类型转换,dynamic_cast`必须在运行时进行一些转换细节操作。因此在使用 dynamic_cast 操作时,应该权衡对性能的影响。

--------------------------------------------------------------------------------------------------------

本文所述工具RTTI(Run-Time Type Identification)主要用于大型程序,因为该工具的使用需要付出一定的开销。

通过使用RTTI,程序可以在运行时通过基类的指针或者引用来得到所指对象的实际类型。主要有两个操作:(1)typeid操作符:返回指针或者引用所指对象的实际类型。(2)dynamic_cast操作符:将基类类型的指针或引用安全地转换为派生类型的指针或者引用。注意:只有当类中至少有一个虚函数时,才能返回我们所需的动态类型信息;否则,只能返回静态类型信息。

dynamic_cast操作符

当dynamic_cast用于基类指针时,该指针必须是有效的——要么是0,要么是指向一个对象。如果绑定到引用或指针的对象不是目标类型的对象,则dynamic_cast失败。如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果是0值;如果转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。因此,dynamic_cast操作符一次执行两个操作:首先验证被请求的转换是否有效,只有当转换有效时才进行实际转换。例如:
Base *pB;
// ...
if (Derived *pD = dynamic_cast<Derived*>(pB) {
// use the Derived object by pD
} else {
// use the Base object by pB
}

Base &b = oneObj;
try {
Derived &d = dynamic_cast<Derived&>(b);
// use the Derived object to which b referred
} catch (bad_cast) {
// not Derived object
}

typeid操作符

typeid表达式形如:
typeid(expr);
这里expr是任意表达式或者类型名。如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表达式的静态类型,在编译时就可以计算。

typeid操作符的返回结果是名为type_info的标准库类型的对象的引用(在头文件typeinfo中定义)。标准并没有确切定义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()
{
cout << 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(Derived).name() << endl
<< typeid(type_info).name() << endl;

return 0;
}


在MinGW2.05下的运行结果:
i
j
l
m
c
h
f
d
Ss
4Base
7Derived
St9type_info

Terminated with return code 0
Press any key to continue ...

注意:当把typeid作用于指针的解引用*p时,若指针p为0,则:如果p指向的类型是带虚函数的类类型,则typeid(*p)在运行时抛出一个bad_typeid异常;否则,typeid(*p)的结果与p的值是不相关的,在编译时就可以确定。typeid表达式的这点性质与sizeof表达式相似但又有区别,sizeof一定是在编译时进行计算,也就是说,其只考虑表达式的静态类型,与表达式的动态类型无关(即使有虚函数存在)。

参考文献:
[1] C++ Primer(Edition 4)
[2] Thinking in C++(Edition 2)
[3] International Standard:ISO/IEC 14882:1998

--------------------------------------------------------------------------------------------------------------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: