参考一:
RTTI(Run-Time Type Identification,通过运行时类型识别)程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
RTTI提供了以下两个非常有用的操作符:
(1)typeid操作符,返回指针和引用所指的实际类型;
(2)dynamic_cast操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。
面向对象的编程语言,象C++,Java,delphi都提供了对RTTI的支持。 本文将简略介绍 RTTI 的一些背景知识、描述 RTTI 的概念,并通过具体例子和代码介绍什么时候使用以及如何使用 RTTI;本文还将详细描述两个重要的 RTTI 运算符的使用方法,它们是 typeid 和 dynamic_cast。
其实,RTTI 在C++中并不是什么新的东西,它早在十多年以前就已经出现了。但是大多数开发人员,包括许多高层次的C++程序员对它并不怎么熟悉,更不用说使用 RTTI 来设计和编写应用程序了。
一些面向对象专家在传播自己的设计理念时,大多都主张在设计和开发中明智地使用虚拟成员函数,而不用 RTTI 机制。但是,在很多情况下,虚拟函数无法克服本身的局限。每每涉及到处理异类容器和根基类层次(如 MFC)时,不可避免要对对象类型进行动态判断,也就是动态类型的侦测。如何确定对象的动态类型呢?答案是使用内建的 RTTI 中的运算符:typeid 和 dynamic_cast。
在C++中存在虚函数,也就存在了多态性,对于多态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用typeid必须使用头文件<typeinfo>,因为typeid是一个返回类型为typ_info的引用的函数所以这里有必要先介绍一下type_info类
typid函数
该函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typid(a).name()就能知道变量a是什么类型的。因为typid()函数是一个反回类型为typid_info类型的函数,所以下面先对type_info类作下介绍
type_info类
该类的具体实现方式依编译器而定,但一般都有如下的成员定义
class type_info
{private:
type_info(const type_info &);
type_info& operator =(const type_info&); //type_info类的复制构造函数和赋值运算符是私有的。
public:
virtual ~type_info(); //析构函数
bool operator = =(const type_info&) const; //在type_info类中重载了= =运算符,该运算符可以比较两个对象的类型是否相等。
bool operator !=(const type_info&)const; //重载的!=运算符,以比较两个对象的类型是否不相等
const char * name() const; //使用得较多的成员函数name,该函数反回对象的类型的名字。前面使用的typeid(a).name()就调用了该成员函数
bool before(const type_info&);};
因为type_info类的复制构造函数和赋值运算符都是私有的,所以不允许用户自已创建type_info的类,比如type_info A;错误,没有默认的构造函数。唯一要使用type_info类的方法就是使用typeid函数。
typeid函数怎样创建type_info类的对象
该函数反回type_info类对象的引用,即形式为type_info& typid();因此也可以说typid函数是type_info类的一个引用对象,可以访问type_info类的成员。但因为不能创建type_info类的对象,而typeid又必须反回一个类型为type_info类型的对象的引用,所以怎样在typeid函数中创建一个type_info类的对象以便让函数反回type_info类对象的引用就成了问题。这可能是把typid函数声明为了type_info类的友元函数来实现的,默认构造函数是私有的并不能阻止该类的友元函数创建该类的对象。所以typeid函数如果是友元的话就可以访问type_info类的私有成员,从而可以创建type_info类的对象,从而可以创建反回类型为type_info类的引用。举个例子class A{private:A(){} A(const A&){} A& operator =(const A&){} friend A& f();};这里把类A的默认构造函数,复制构造函数和赋值操作符定为私有从而防止创建类A的对象,但函数f()是类A的友元,所以在函数f()中可以创建类A的对象。同时为了实现函数f()反回的对象类型是A的引用,就必须在函数f中创建一个类A的对象以作为函数f的反回值,比如函数f可以这样定义A& f(){A ma; cout<<”f”<<endl; return ma}。
因为typeid函数是type_info类的对象,也就是说可以用该函数访问type_info类的成员,即type_info类中重载的= =和!=运算符,name()和before()成员函数,比如typid(a).naem()和typid(a)= =typid(b)等等。
typeid函数的使用原理
该函数的形式为type_info& typeid(object)其中object是任何类型的对象,可以是内置类型和用户创建的类类型。可以看出typeid即是一个函数,同时他也是type_info类的对象,即typeid可以访问类type_info类的成员,也可以做为一个单独的函数来使用。做个简单的例子,比如
class A{private: A(){b=3;cout<<”A”<<endl;} //私有的默认构造函数
public: void name(){cout<<”NA”<<endl;} int b;
friend A f();}; //函数f()是类A的友元,因此在f中可以创建类A的对象。
A f() //函数f()在这里即是类A的一个对象,也是一个单独的函数。
{ A m; //创建类A的对象,因为函数f是类A的友元,因此可以创建类A的对象
cout<<”F”<<endl; return m;}
main()
{ f().name(); //函数f()作为类A的对象使用,这里要注意程序的执行顺序,首先执行函数f()中的语句A m,因此调用类A的默认构造函数输出A,然后执行A m;后面的语句,输出F,再然后调用类A中的成员函数name输出NA.
f(); } //函数f()单独作为函数使用。
我们创建一个类A,其中A的默认构造函数是私有的,也就是说不能用默认构造函数创建类A的对象。函数f()是类A的友元,且反回一个类A的对象,因为f()函数是类A的友元,所以在函数f中可以用默认构造函数创建类A的对象,这时函数f()同时是一个函数,也是类A的对象,因此也可以访问类A中的成员。
typeid函数使用方式
1)、使用type_info类中的name()成员函数反回对象的类型的名称。其方法为:typeid(object).name()其中object是要显示的对象的类型名,该函数反回的名字因编译器而定。这里要注意的就是使用方式一中提到的虚函数类型的问题,即如果有类A,且有虚函数,类B,C,D都是从类A派生的,且都重定义了类A中的虚函数,这时有类A的指针p,再把对象类B的对象的地址赋给指针p,则typeid(p).name()将反回的类型将是A*,因为这里的p表示的是一个指针,该指针是类型为A的指针,所以反回A*,而typeid(*p).name()将反回B,因为指针p是指向类B的对象的,而*p就表示的是类B的对象的类型,所以反回B。
2)、使用type_info类中重载的= =与!=比较两个对象的类型是否相等。使用该方法需要调用类type_info中重载的= =和!=操作符,其使用方法为typid(object1)= =typid(object2);如果两个对象的类型相等则反回1,如果不相等则为0。这种使用方法通常用于比较两个带有虚函数的类的对象是否相等,比如有类A,其中定义有虚函数,而类B,类C,类D,都是从类A派生而来的且重定义了该虚函数,这时有个类A的指针p和p1,按照虚函数的原理,基类的指针可以指向任何派生类的对象,在这时就有可能需要比较两个指针是否指向同一个对象,这时就可以这样使用typeid了,typeid(*p)= =typeid(*p1);这里要注意的是typeid(*p)与typeid(p)是指的不同的对象类型,typeid(p)表示的是p的类型,在这里p是一个指针,这个指针指向的是类A的对象,所以p的类型是A*,而typeid(*p)则不一样,*p表示的是指针p实际所指的对象的类型,比如这里的指针p指向派生类B,则typeid(*p)的类型为B。所以在测试两个指针的类型是否是相等时应使用*p,即typeid(*p)= =typeid(*p1)。如果是typeid(p)= =typeid(p1)的话,则无论指针p和p1指向的什么派生类对象,他们都是相等的,因为都是A *的类型。
强制类型转换运算符
C++有四种强制类型转换符,分别是dynamic_cast,const_cast,static_cast,reinterpret_cast。其中dynamic_cast与运行时类型转换密切相关,在这里我们先介绍dynamic_cast,其他三种在后面介绍。
dynamic_cast强制转换运算符
该转换符用于将一个指向派生类的基类指针或引用转换为派生类的指针或引用,注意dynamic_cast转换符只能用于含有虚函数的类,其表达式为dynamic_cast<类型>(表达式),其中的类型是指把表达式要转换成的目标类型,比如含有虚函数的基类B和从基类B派生出的派生类D,则B *pb; D *pd, md; pb=&md; pd=dynamic<D*>(pb); 最后一条语句表示把指向派生类D的基类指针pb转换为派生类D的指针,然后将这个指针赋给派生类D的指针pd,有人可能会觉得这样做没有意义,既然指针pd要指向派生类为什么不pd=&md;这样做更直接呢?有些时候我们需要强制转换,比如如果指向派生类的基类指针B想访问派生类D中的除虚函数之外的成员时就需要把该指针转换为指向派生类D的指针,以达到访问派生类D中特有的成员的目的,比如派生类D中含有特有的成员函数g(),这时可以这样来访问该成员dynamic_cast<D*>(pb)->g();因为dynamic_cast转换后的结果是一个指向派生类的指针,所以可以这样访问派生类中特有的成员。但是该语句不影响原来的指针的类型,即基类指针pb仍然是指向基类B的。如果单独使用该指针仍然不能访问派生类中特有的成员。一般情况下不推见这样使用dynamic_cast转换符,因为dynamic_cast的转换并不会总是成功的,具体情况在后面介绍。
dynamic_cast的注意事项
dynamic_cast转换符只能用于指针或者引用。dynamic_cast转换符只能用于含有虚函数的类。dynamic_cast转换操作符在执行类型转换时首先将检查能否成功转换,如果能成功转换则转换之,如果转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常,所以在使用dynamic_cast转换之间应使用if语句对其转换成功与否进行测试,比如pd=dynamic_cast<D*>(pb); if(pd){…}else{…},或者这样测试if(dynamic_cast<D*>(pb)){…}else{…}。
const_cast操作符
其表达式为const_cast<类型>(表达式),其中类型指要把表达式转换为的目标类型。该操作符用于改变const和volatile,const_cast最常用的用途就是删除const属性,如果某个变量在大多数时候是常量,而在某个时候又是需要修改的,这时就可以使用const_cast操作符了。const_cast操作符不能改变类型的其他方面,他只能改变const或volatile,即const_cast不能把int改变为double,但可以把const int改变为int。const_cast只能用于指针或引用。const_cast的用法举例比如:int a=3; const int *b=&a; int* c; c=const_cast<int*>(b); *c=4; cout<<a<<*c;这时输出两个4,如果不使用const_cast转换符则常量指针*c的值是不能改变的,在这里使用const_cast操作符,通过指针b就能改变常量指针和变量a的值。
static_cast操作符
该操作符用于非多态类型的转换,任何标准转换都可以使用他,即static_cast可以把int转换为double,但不能把两个不相关的类对象进行转换,比如类A不能转换为一个不相关的类B类型。static_cast本质上是传统c语言强制转换的替代品。
reinterpret_cast操作符
该操作符用于将一种类型转换为另一种不同的类型,比如可以把一个整型转换为一个指针,或把一个指针转换为一个整型,因此使用该操作符的危险性较高,一般不应使用该操作符。
9、使用 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 操作时,应该权衡对性能的影响。
参考二:
c++中RTTI的观念和使用
下面这篇文章虽然有点老,但对C++的RTTI基本原理讲的比较透彻。
该文章摘自UMLCHINA网站,是台湾一个群体写的,我根据大家比较熟悉的方式,修改了一些名词的说法,如衍生(派生)等,让大家可以方便的阅读。
C++的 RTTI 观念和用途
物泽C++应用小组
自从1993年Bjarne Stroustrup 〔注1 〕提出有关C++ 的RTTI功能之建议﹐以及C++
的异常处理(exception handling)需要RTTI﹔最近新推出的C++ 或多或少已提供RTTI。然而,若不小心使用RTTI﹐可能会导致软件弹性的降低。本文将介绍RTTI的观念和近况﹐并说明如何善用它。
什么是RTTI﹖
在C++ 环境中﹐头文件(header file) 含有类之定义(class definition)亦即包含有
关类的结构资料(representational information)。但是﹐这些资料只供编译器(compi
ler)使用﹐编译完毕后并未留下来﹐所以在执行时期(at run-time) ﹐无法得知对象的
类资料﹐包括类名称、数据成员名称与类型、函数名称与类型等等。例如﹐两个类﹐其继承关系如下图:
若有如下指令﹕
Figure *p;
p = new Circle();
Figure &q = *p;
在执行时﹐p 指向一个对象﹐但欲得知此对象之类资料﹐就有困难了。同样欲得知q 所参考(reference) 对象的类资料﹐也无法得到。
RTTI(Run-Time Type Identification)就是要解决这困难﹐也就是在执行时﹐您想知
道指针所指到或参考到的对象类型时﹐该对象有能力来告诉您。
随着应用场合之不同﹐所需支持的RTTI范围也不同。最单纯的RTTI包括﹕
●类识别(class identification)──包括类名称或ID。
●继承关系(inheritance relationship)──支持执行时期的「往下变换类型」(downw
ard casting)﹐亦即动态变换类型(dynamic casting) 。
在对象数据库存取上﹐还需要下述RTTI﹕
●对象结构(object layout) ──包括属性的类型、名称及其位置(position或offset
)。
●成员函数表(table of functions)──包括函数的类型、名称、及其参数类型等。
其目的是协助对象的I/O 和持久化(persistence) ﹐也提供调试讯息等。
若依照Bjarne Stroustrup 之建议〔注1 〕﹐C++ 还应包括更完整的RTTI﹕
●能得知类所实例化的各对象 。
●能参考到函数的源代码。
●能取得类的有关在线说明(on-line documentation) 。
其实这些都是C++ 编译完成时﹐所丢弃的资料﹐如今只是希望寻找个途径来将之保留到执行期间。然而﹐要提供完整的RTTI﹐将会大幅提高C++ 的复杂度﹗
RTTI可能伴随的副作用
RTTI最主要的副作用是﹕程序员可能会利用RTTI来支持其「复选」(multiple-select
ion)方法﹐而不使用虚函数(virtual function)方法。
虽然这两种方法皆能达到多态化(polymorphism) ﹐但使用复选方法﹐常导致违反著名的「开放╱封闭原则」(open/closed principle) 〔注2 〕。反之﹐使用虚函数方法则可合乎这个原则, 请看下图﹕
Circle和Square皆是由Figure所派生出来的子类﹐它们各有自己的draw()函数。当
C++ 提供了RTTI﹐就可写个函数如下﹕
void drawing( Figure *p )
{
if( typeid(*p).name() == "Circle" )
((Circle*)p) -> draw();
if( typeid(*p).name() == "Rectangle" )
((Rectangle*)p) -> draw();
}
虽然drawing() 函数也具有多型性﹐但它与Figure类体系的结构具有紧密的相关性。
当Figure类体系再派生出子类时﹐drawing() 函数的内容必须多加个if指令。因而违反
了「开放╱封闭原则」﹐如下﹕
很显然地﹐drawing() 函数应加以修正。
想一想﹐如果C++ 并未提供RTTI﹐则程序员毫无选择必须使用虚函数来支持drawing() 函数的多型性。于是程序员将draw()宣告为虚函数﹐并写drawing() 如下﹕
void drawing(Figure *p)
{ p->draw(); }
如此﹐Figure类体系能随时派生类﹐而不必修正drawing() 函数。亦即﹐Figure体
系有个稳定的接口(interface) ﹐drawing() 使用这接口﹐使得drawing() 函数也稳定
﹐不会随Figure类体系的扩充而变动。这是封闭的一面。而这稳定的接口并未限制Figure体系的成长﹐这是开放的一面。因而合乎「开放╱封闭」原则﹐软件的结构会更具弹性﹐更易于随环境而不断成长。
RTTI的常见的 使用场合
一般而言﹐RTTI的常见使用场合有四﹕异常处理(exceptions handling)、动态转
类型(dynamic casting) 、模块集成、以及对象I/O 。
1.异常处理── 大家所熟悉的C++ 新功能﹕异常处理﹐其需要RTTI﹐如类名称等。
2.动态转类型── 在类体系(class hierarchy) 中﹐往下的类型转换需要类继承的RT
TI。
3.模块集成── 当某个程序模块里的对象欲跟另一程序模块的对象沟通时﹐应如何得知对方的身分呢﹖知道其身分资料﹐才能呼叫其函数。一般的C++ 程序﹐常见的解决方法是──在源代码中把对方对象之类定义(即存在头文件里)包含进来﹐在编译时进行连结工作。然而﹐像目前流行的主从(Client-Server) 架构中﹐客户端(client)的模块对象﹐常需与主机端(server)的现成模块对象沟通﹐它们必须在执行时沟通﹐但又常无法一再重新编译。于是靠标头文件来提供的类定义资料﹐无助于执行时的沟通工作﹐只得依赖RTTI了。
4.对象I/O ── C++ 程序常将其对象存入数据库﹐未来可再读取之。对象常内含其它
小对象﹐因之在存入数据库时﹐除了必须知道对象所属的类名称﹐也必须知道各内含小对象之所属类﹐才能完整地将对象存进去。储存时﹐也将这些RTTI资料连同对象内容一起存入数据库中。未来﹐读取对象时﹐可依据这些RTTI资料来分配内存空间给对象。
RTTI从那里来﹖
上述谈到RTTI的用途﹐以及其副作用。这众多争论﹐使得RTTI的标准迟迟未呈现出来。也导致各C++ 开发环境提供者﹐依其环境所需而以各种方式来支持RTTI﹐且其支持RTTI的范围也所不同。 目前常见的支持方式包括﹕
●由类库提供RTTI──例如﹐Microsoft 公司的Visual C++环境。
●由C++ 编译器(compiler)提供──例如﹐Borland C++ 4.5 版本。
●由源代码产生器(code generator)提供──例如Bellvobr系统。
●由OO数据库的特殊预处理器(preprocessor)提供──例如Poet系统。
●由程序员自己加上去。
这些方法皆只提供简单的RTTI﹐其仅为Stroustrup先生所建议RTTI内涵的部分集合而已。相信不久的将来﹐会由C++ 编译器来提供ANSI标准的RTTI﹐但何时会订出这标准呢﹖没人晓得吧﹗
程序员自己提供的RTTI
通常程序员自己可提供简单的RTTI﹐例如提供类的名称或识别(TypeID)。最常见的方法是﹕为类体系定义些虚函数如Type_na() 及Isa() 函数等。请先看个例子﹕
class Figure { };
class Rectangle : public Figure { };
class Square : public Rectangle
{ int data;
public:
Square() { data=88; }
void Display() { cout << data << endl; }
};
void main()
{ Figure *f = new Rectangle();
Square *s = (Square *)f;
s -> Display();
}
这时s 指向Rectangle 之对象﹐而s->Display()呼叫Square::Display() ﹐将找不到da
ta值。若在执行时能利用RTTI来检查之﹐就可发出错误讯息。于是﹐自行加入RTTI功能
﹕
class Figure
{ public:
virtual char* Type_na()
{ return "Figure"; }
virtual int Isa(char* cna)
{ return !strcmp(cna, "Figure")? 1:0; }
};
class Rectangle:public Figure
{ public:
virtual char* Type_na()
{ return "Rectangle"; }
virtual int Isa(char* cna)
{ return !strcmp(cna, "Rectangle")?
1 : Figure::Isa(cna);
}
static Rectangle* Dynamic_cast(Figure* fg)
{ return fg -> Isa(Type_na())?
(Rectangle*)fg : 0;
}
};
class Square:public Rectangle
{ int data;
public:
Square() { data=88; }
virtual char* Type_na()
{ return "Square"; }
virtual int Isa(char* cna)
{ return !strcmp(cna, "Rectangle")?
1 : Rectangle::Isa(cna);
}
static Square* Dynamic_cast(Figure *fg)
{ return fg->Isa(Type_na())?
(Square*)fg : 0;
}
void Display() { cout << "888" << endl; }
};
虚函数Type_na() 提供类名称之RTTI﹐而Isa() 则提供继承之RTTI﹐用来支持「动态转类型」函数──Dynamic_cast()。例如﹕
Figure *f = new Rectangle();
cout << f -> Isa("Square") << endl;
cout << f -> Isa("Figure") << endl;
这些指令可显示出﹕f 所指向之对象并非Square之对象﹐但是Figure之对象(含子孙对象)。再如﹕
Figure *f; Square *s;
f = new Rectangle();
s = Square == Dynamic_cast(f);
if(!s)
cout << "dynamic_cast error!!" << endl;
此时﹐依RTTI来判断出这转类型是不对的。
类库提供RTTI
由类库提供RTTI是最常见的﹐例如Visual C++的MFC 类库内有个CRuntimeClass 类﹐其内含简单的RTTI。请看个程序﹕
class Figure:public CObject
{
DECLARE_DYNAMIC(Figure);
};
class Rectangle : public Figure
{
DECLARE_DYNAMIC(Rectangle);
};
class Square : public Rectangle
{
DECLARE_DYNAMIC(Square);
int data;
public:
void Display() { cout << data << endl; }
Square() { data=88; }
};
IMPLEMENT_DYNAMIC(Figure, CObject);
IMPLEMENT_DYNAMIC(Rectangle, Figure);
IMPLEMENT_DYNAMIC(Square, Rectangle);
Visual C++程序依赖这些宏(Macor) 来支持RTTI。现在就看看如何使用CRuntimeClass
类吧﹗如下﹕
CRuntimeClass *r;
Figure *f = new Rectangle();
r = f -> GetRuntimeClass();
cout << r -> m_psClassName << endl;
这就在执行时期得到类的名称。Visual C++的类库仅提供些较简单的RTTI──类名称、对象大小及父类等。至于其它常用的RTTI如──数据项的类型及位置(position)等皆未提供。
C++编译器提供RTTI
由C++ 语言直接提供RTTI是最方便了﹐但是因RTTI的范围随应用场合而不同﹐若C++语言提供所有的RTTI﹐将会大幅度增加C++ 的复杂度。目前﹐C++ 语言只提供简单的RTTI﹐例如Borland C++ 新增typeid()操作数以及dynamic_cast<T*>函数样版。请看个程序﹕
class Figure
{ public:
virtual void Display();
};
class Rectangle : public Figure { };
class Square:public Rectangle
{ int data;
public:
Square() { data=88; }
void Display() { cout << data << endl; }
};
现在看看如何使用typeid()操作数──
Figure *f = new Square();
const typeinfo ty = typeid(*f);
cout << ty.name() << endl;
这会告诉您﹕f 指针所指的对象﹐其类名称是Square。再看看如何使用dynamic_cast<T*>函数样版──
Figure *f; Square *s;
f = new Rectangle();
s = dynamic_cast<Sqiare *>(f);
if(!s)
cout << "dynamic casting error!!" << endl;
在执行时﹐发现f 是不能转为Square *类型的。如下指令﹕
Figure *f; Rectangle *r;
f = new Square();
r = dynamic_cast<Rectangle *>(f);
if(r) r->Display();
这种类型转换是对的。
RTTI与虚函数表
在C++ 程序中﹐若类含有虚函数﹐则该类会有个虚函数表(Virtual Function Table﹐
简称VFT )。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐这对象内含RTTI资料,如下图:
由于该类所实例化之各对象﹐皆含有个指针指向VFT 表﹐因之各对象皆可取出typeinfo对象而得到RTTI。例如﹐
Figure *f1 = new Square();
Figure *f2 = new Square();
const typeinfo ty = typeid(*f2);
其中﹐typeid(*f2) 的动作是﹕
1.取得f2所指之对象。
2.从对象取出指向VMF 之指针﹐经由此指针取得VFT 表。
3.从表中找出指向typeinfo对象之指针﹐经由此指针取得typeinfo对象。
这typeinfo对象就含有RTTI了。参考下图1,经由f1及f2两指针皆可取得typeinfo对象﹐所以 typeid(*f2) == typeid(*f1)。
总结
RTTI是C++ 的新功能。过去﹐C++ 语言来提供RTTI时﹐大多依赖类库来支持﹐但各类库使用的方法有所不同﹐使得程序的可移植性(portability) 大受影响。然而﹐目前C++ 也只提供最简单的RTTI而已﹐可预见的未来﹐当大家对RTTI的意见渐趋一致时﹐C++ 将会提供更完整的RTTI﹐包括数据项和成员函数的类型、位置(offset)等资料﹐使得C++ 程序更井然有序﹐易于维护。
参考资料
[注1] Stroustrup B., “Run-Time Type Identification for C++”, Usenix C++ C
onference, Portland, 1993.
[注2] Meyer B.,Object-Oriented Software Construction, Prentice Hall, 1988.█
参考三:
C++之RTTI
RTTI(Run-Time Type Identification 运行时类型识别)是面向对象程序设计中一种重要的技术。
和很多其他语言一样,C++是一种静态类型语言。其数据类型是在编译期就确定的,不能在运行时更改。然而由于面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)本身的类型,可能与它实际代表(指向或引用)的类型并不一致(比如基类指针指向派生类对象)。有时我们需要将一个多态指针转换为其实际指向对象的类型,就需要知道运行时的类型信息,这就产生了RTTI的要求。
C++提供了两个关键字typeid和dynamic_cast和一个type_info类来支持RTTI.
dynamic_cast操作符
它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构安全地转换类型(主要是安全向下转型)。dynamic_cast提供了两种转换方式,把基类指针(或引用)转换成派生类指针(或引用)。如:
class BaseClass
{
public:
virtual ~BaseClass(){}
virtual void dispaly(){}
};
class DerivedClass : public BaseClass
{
public:
virtual ~DerivedClass(){}
virtual void dispaly(){}
};
DerivedClass obj;
BaseClass* pB = &obj;
DerivedClass* pD = dynamic_cast<DerivedClass*>(pB);
typeid操作符
指出指针或引用指向的对象的实际类型。typeid可以用于作用于各种类型名,对象和内置基本数据类型的实例、指针或者引用,当作用于指针和引用将返回它实际指向对象的类型信息。typeid操作符的返回结果是名为type_info的标准库类型的对象的引用。
type_info类:这个类的确切定义是与编译器实现相关的,在头文件typeinfo中定义。
typeid(typename); //typename为类型名,如int
typeid(expr); //expr为表达式,如果表达式的类型是类类型且至少包含有一个虚函数,则typeid操作符返回表达式的动态类型,需要在运行时计算;否则,typeid操作符返回表达式的静态类型,在编译时就可以计算。
type_info的默认构造函数和拷贝构造函数及赋值操作符都定义为private,所以不能定义或复制type_info类型的对象。程序中创建type_info对象的唯一方法是使用typeid操作符.type_info的name成员函数返回C-style的字符串,用来表示相应的类型名,但务必注意这个返回的类型名与程序中使用的相应类型名并不一定一致,具体由编译器的实现所决定,标准只要求实现为每个类型返回唯一的字符串。
例如,在Codeblocks 8.02上试验,结果如下:
BaseClass bObj, *pb=NULL;
DerivedClass dObj;
cout<< typeid(bool).name() << endl
<< typeid(char).name() << endl
<< typeid(unsigned char).name() << endl
<< typeid(short).name() << endl
<< typeid(unsigned short).name() << endl
<< typeid(int).name() << endl
<< typeid(unsigned int).name() << endl
<< typeid(long).name() << endl
<< typeid(unsigned long).name() << endl
<< typeid(long long).name() << endl
<< typeid(float).name() << endl
<< typeid(double).name() << endl
<< typeid(void).name() << endl
<< typeid(void*).name() << endl
<< typeid(string).name() << endl
<< typeid(BaseClass).name() << endl
<< typeid(DerivedClass).name() << endl
<< typeid(bObj).name()<<endl
<< typeid(pb).name()<<endl
<< typeid(dObj).name()<<endl;
大致规律是:
1.内置类型一般先是第一个字母
2.相应的Unsigned的内置类型是其第一个字母的后一个字母,如unsigned int是j
3.内置类型指针是在其第一个字母前加一个p,如void*是Pv
4.C++的类类型是显示其名称
5.用户自定义类型是表示名称字母个数的数字加上完整的名称
如果typeid操作符的操作数是至少包含一个虚拟函数的类类型时,并且该表达式是一个基类的应用,则typeid操作符指出底层实际对象的派生类类型。
DerivedClass dObj;
BaseClass *pb = dynamic_cast<BaseClass*>(new DerivedClass);
BaseClass &rb = dObj;
DerivedClass *pd = &dObj;
DerivedClass *pd1 = dynamic_cast<DerivedClass*>(pb);
cout << typeid(pb).name() <<endl
<< typeid(*pb).name()<<endl
<< typeid(rb).name()<<endl
<< typeid(pd).name()<<endl
<< typeid(*pd1).name()<<endl;
参考四:
关于C++中RTTI的思考
RTTI(运行时类型信息),是所有RAD开发工具的基础。 这段时间,因为需要在自己的软件中加入扩展的RTTI功能,于是专门对RTTI进行了研究,现在一些心得写下来。
RTTI是比较新的编程语言提供的能力,C、Fortran、Basic就没有,而现在几乎所有现代的、可视化的编程语言都提供了RTTI功能。C++的标准中也对RTTI作了规定。但相对其它语言而言,C++的RTTI信息是最简单的。
一、RTTI典型的应用需求
1、类型的识别,即能在运行时判断出某对象、表达式等的类型,能判断它们是基本类型(int、string),还是对象,以及它们区别于其它类型的标识;
2、对象的继承关系的运行时判断;
3、在出错处理、内存诊断等处理时的输出信息;
4、基于字符型名称的运行时对象访问、方法调用;
5、对象的自动保存和读入;
6、基于ID或名称的对象自动生成;
7、环境配置的保存和读入;
8、程序自动生成;
二、C++中RTTI的实现
RTTI最直接的实现是通过编程语言的语法支持功能,由编译器自动完成,比如:Delphi等语言就提供了Property关键字。C++语言没有这些语法支持功能,因为C++不是一种RAD语言,可以将VB、Delphi、Java等与之进行对比。C++的RTTI是最简单的,只能获得类名和相关的继承信息;而VB、Delphi、Java等确复杂得多,甚至于支持属性名、方法名、事件名等。
标准C++ 提供了typeid() 操作,以得到类型信息,它的参数可以是一个表达式,可以是一个对象、指针或者引用,通过这个方法,可以得到一个指向常type_info对象,里面包含了这个表达式的类型必要的信息。type_info对象提供的功能有如下这些:
1、name(),可以得到一个包含类型信息的字符串,返回如:"int"、"MyClass"等;
2、before(),用来在类型列表中遍历;
3、==操作,用来判断类型是否相同;
VC中的MFC提供的RTTI实现与C++提供的差不多,但是有一些功能上和实现在的差别,但也只是提供了名称和比较功能。
不能说,这是C++或VC中的缺陷,这是由C++的定位决定了,如果C++加上了复杂的RTTI信息,则C++可能就变成了C#或别的什么语言了。是的,C#增加了比较复杂的RTTI信息,谁让他的主设计者就是从Borland公司过去的呢(这也决定了C++在.NET框架中的会被淘汰的命运,因为它在.NET中没有合适的定位,就象VF被淘汰一样)。
三、RTTI实现的要点
作为一种语言的新特性,RTTI的实现,应有如下要求:
1、必须满足特定语言的定位和要求,不能说,将所有可能的功能加进去就是好东西了;
2、必须是尽可能透明的,RTTI的主要应用在IDE和底层,一般情况下,编程用户不需要了解过多的RTTI细节,比如:作为Delphi用户,他只需要基于属性的访问方法,只需要知道基于组件的保存方法,不需要了解RTTI在其中所起的作用,就象电视机的使用者不需要了解电视机的原理;
3、尽可能轻便,不能因为实现RTTI要耗费大量内存和CPU时间,不能占用太大的程序空间,当然,这个要求是相对的,
4、尽可能高效
4、作为RTTI的实现者,应该尽可能在定义了功能集的情况下,以幽雅的方法实现之。
C++中实现RTTI和执久化是通过宏来完成的,幽雅吗?算是吧。
四、关于几个库中RTTI实现的评价
1、MFC,MFC实现了简单的,与标准C++有区别的RTTI,其实现是通过宏来完成的,同时通过宏来辅助用户完成执久化工作,但执久化
的具体细节要求用户完成。
2、VCL,VCL实现了比较复杂的RTTI,具有关系继承、属性、方法、事件、基于属性的自动对象生成、强大的执久化功能;
3、COM,COM通过类型库定义其RTTI,比VCL还要复杂。
4、QT, QT中实现的RTTI也是比较复杂的,值得说明的是,它提供了一种特别的扩展方法,在编译前,必须将其RTTI信息转换为标准C++语言,个人认为,这是一种不好的方法,虽然我对QT库的评价非常高。特别是它提供的富有特色的CANVAS功能(写到这里,想起以前使用 Oracle的扩展C语言时的痛苦)。
5、WXWINDOW,实现的是比较简单的RTTI。
6、VCF,实现了我所看到的在C++中最全面的RTTI功能,但其性能是一个大问题。
7、OOPS,实现了我所看到的在C++中对象执久化全好的解决方案,但它的扩充性不好在已有标准库的基础上能很好地完成工作,但在增加第三方库的时侯就痛苦了(对了,OOPS中有一篇文章,对RTTI的需求进行了定义,写得非常好http://www.rcs.hu/Articles/RTTI_Part1.htm)。
五、附言
我为什么对RTTI比较关注呢,因为我需要在自己的C++中增加一个轻型的RTTI系统,目前这个体系统正在设计中。