RTTI(Runtime Type Information )

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 操做时,应该权衡对性能的影响html

第二章c++

typeid和RTTI C++- -程序员

Tag: typeid和RTTI    C++                                          算法

观点有一些值得商榷的地方编程

关于typeid和RTTI的问答 
问:在c++里怎么能知道一个变量的具体类型,如:c#里的typeof.还有我怎么知道一个变量的类型是某个类型的子类,也就是实现关键字ISc#

答:
1。运行时获知变量类型名称,可使用 typeid(变量).name,须要注意不是全部编译器都输出"int"、"float"等之类的名称,对于这类的编译器能够这样使用:float f = 1.1f; if( typeid(f) == typeid(0.0f) ) ……
2。对于多态类实例,想获得实际的类名称,须要使用到RTTI,这须要在编译的时候加上参数"/GR"。
3。对于普通变量,既然是本身写的,那固然也就应该知道它的类型,其实用不着运行时获知;对于多态类实例,既然须要运行时获知实际类型,那么就说明这里不具备多态性,既然没有多态性就不该该抽象它,这属于设计错误,总之,我认为RTTI是多余的。
4。对于多态类实例,使用 typeid(value) == typeid(value)来判断,不如使用 dynamic_cast 来判断,它们的原理是同样的。数组

事例代码:
#include 
using namespace std;
int main( void )
{
// sample 1
    cout << typeid(1.1f).name() << endl;
// sample 2
    class Base1
    {
    };
    class Derive1 : public Base1
    {
    };
    Derive1 d1;
    Base1& b1 = d1;
    cout << typeid(b1).name() << endl; // 输出"class Base1",由于Derive1和Base1之间没有多态性
// sample 3, 编译时须要加参数 /GR
    class Base2
    {
        virtual void fun( void ) {}
    };
    class Derive2 : public Base2
    {
    };
    Derive2 d2;
    Base2& b2 = d2;
    cout << typeid(b2).name() << endl; // 输出"class Derive2",由于Derive1和Base1之间有了多态性
// sample 4
    class Derive22 : public Base2
    {
    };
    // 指针强制转化失败后能够比较指针是否为零,而引用却没办法,因此引用制转化失败后抛出异常
    Derive2* pb1 = dynamic_cast(&b2);
    cout << boolalpha << (0!=pb1) << endl; // 输出"true",由于b2自己就确实是Derive2
    Derive22* pb2 = dynamic_cast(&b2);
    cout << boolalpha << (0!=pb2) << endl; // 输出"true",由于b2自己不是Derive2安全

    try {
        Derive2& rb1 = dynamic_cast<DERIVE2& />(b2);
        cout << "true" << endl;
    } catch( bad_cast )
    {
        cout << "false" << endl;
    }
    try {
        Derive22& rb2 = dynamic_cast<DERIVE22& />(b2);
        cout << "true" << endl;
    } catch( ... ) // 应该是 bad_cast,但不知道为何在VC++6.0中却不行
    {
        cout << "false" << endl;
    }数据结构

    return 0;
}app

posted on 2004-09-13 22:45 周星星 阅读(2278) 评论(9)  编辑 收藏


/////////////////////////////////////////////
MFC六大关键技术之运行时类型识别
运行时类型识别(RTTI)便是程序执行过程当中知道某个对象属于某个类,咱们平时用C++编程接触的RTTI通常是编译器的RTTI

运行时类型识别(RTTI)便是程序执行过程当中知道某个对象属于某个类,咱们平时用C++编程接触的RTTI通常是编译器的RTTI,便是在新版本的VC++编译器里面选用“使能RTTI”,而后载入typeinfo.h文件,就可使用一个叫typeid()的运算子,它的地位与在C++编程中的sizeof()运算子相似的地方(包含一个头文件,而后就有一个熟悉好用的函数)。typdid()关键的地方是能够接受两个类型的参数:一个是类名称,一个是对象指针。因此咱们判别一个对象是否属于某个类就能够象下面那样:

if (typeid (ClassName)== typeid(*ObjectName)){
((ClassName*)ObjectName)->Fun();
}

  象上面所说的那样,一个typeid()运算子就能够轻松地识别一个对象是否属于某一个类,但MFC并非用typeid()的运算子来进行动态类型识别,而是用一大堆使人费解的宏。不少学员在这里很疑惑,好象MFC在大部分地方都是故做神秘。使们你们编程时很迷惘,只知道在这里加入一组宏,又在那儿加入一个映射,而不知道咱们为何要加入这些东东。

  其实,早期的MFC并无typeid()运算子,因此只能沿用一个老办法。咱们甚至能够想象一下,若是MFC早期就有template(模板)的概念,可能更容易解决RTTI问题。

  因此,咱们要回到“古老”的年代,想象一下,要完成RTTI要作些什么事情。就好像咱们在一个新型(新型到咱们还不认识)电器公司里面,咱们要识别哪一个是电饭锅,哪一个是电磁炉等等,咱们要查看登记的各电器一系列的信息,咱们才能够比较、鉴别,那个东西是什么!
要登记一系列的消息并非一件简单的事情,你们可能首先想到用数组登记对象。但若是用数组,咱们要定义多大的数组才好呢,大了浪费空间,小了更加不行。因此咱们要用另外一种数据结构——链表。由于链表理论上可大可小,能够无限扩展。

  链表是一种经常使用的数据结构,简单地说,它是在一个对象里面保存了指向下一个同类型对象的指针。咱们大致能够这样设计咱们的类:

struct CRuntimeClass
{
……类的名称等一切信息……
CRuntimeClass * m_pNextClass;//指向链表中下一CRuntimeClass对象的指针
};

  链表还应该有一个表头和一个表尾,这样咱们在查链表中各对象元素的信息的时候才知道从哪里查起,到哪儿结束。咱们还要注明自己是由哪能个类派生。因此咱们的链表类要这样设计:

struct CRuntimeClass
{
……类的名称等一切信息……
CRuntimeClass * m_pBaseClass;//指向所属的基类。
CRuntimeClass * m_pNextClass;//定义表尾的时候只要定义此指针为空就能够 了。
static CRuntimeClass* pFirstClass;//这里表头指针属于静态变量,由于咱们知道static变量在内存中只初始化一次,就能够为各对象所用!保证了各对象只有一个表头。
};

  有了CRuntimeClass结构后,咱们就能够定义链表了:

static CRuntimeClass classCObject={NULL,NULL};//这里定义了一个CRuntimeClass对象,

  由于classCObject无基类,因此m_pBaseClass为NULL。由于目前只有一个元素(即目前没有下一元素),因此m_pNextClass为NULL(表尾)。

  至于pFirstClass(表头),你们可能有点想不通,它到什么地方去了。由于咱们这里并不想把classCObject做为链表表头,咱们还要在前面插入不少的CRuntimeClass对象,而且由于pFirstClass为static指针,便是说它不是属于某个对象,因此咱们在用它以前要先初始化:

CRuntimeClass* CRuntimeClass::pFirstClass=NULL;

  如今咱们能够在前面插入一个CRuntimeClass对象,插入以前我得重要申明一下:若是单纯为了运行时类型识别,咱们未必用到m_pNextClass指针(更可能是在运行时建立时用),咱们关心的是类自己和它的基类。这样,查找一个对象是否属于一个类时,主要关心的是类自己及它的基类:

CRuntimeClass classCCmdTarget={ &classCObject, NULL};
CRuntimeClass classCWnd={ &classCCmdTarget ,NULL };
CRuntimeClass classCView={ &classCWnd , NULL };

  好了,上面只是仅仅为一个指针m_pBaseClass赋值(MFC中真正CRuntimeClass有多个成员变量和方法),就链接成了链表。假设咱们如今已所有构造完成本身须要的CRuntimeClass对象,那么,这时候应该定义表头。即要用pFirstClass指针指向咱们最后构造的CRuntimeClass对象——classCView。

CRuntimeClass::pFirstClass=&classCView;

  如今链表有了,表头表尾都完善了,问题又出现了,咱们应该怎样访问每个CRuntimeClass对象?要判断一个对象属于某类,咱们要从表头开始,一直向表尾查找到表尾,而后才能比较得出结果吗。确定不是这样!

  你们能够这样想一下,咱们构造这个链表的目的,就是构造完以后,可以按主观地拿一个CRuntimeClass对象和链表中的元素做比较,看看其中一个对象中否属于你指定的类。这样,咱们须要有一个函数,一个能返回自身类型名的函数GetRuntimeClass()。

  上面简单地说一下链表的过程,但单纯有这个链表是没有任何意义。回到MFC中来,咱们要实现的是在每一个须要有RTTI能力的类中构造一个CRuntimeClass对象,比较一个类是否属于某个对象的时候,实际上只是比较CRuntimeClass对象。

  如何在各个类之中插入CRuntimeClass对象,而且指定CRuntimeClass对象的内容及CRuntimeClass对象的连接,这里起码有十行的代码才能完成。在每一个须要有RTTI能力的类设计中都要重复那十多行代码是一件乏味的事情,也容易出错,因此MFC用了两个宏代替这些工做,即DECLARE_DYNAMIC(类名)和IMPLEMENT_DYNAMIC(类名,基类名)。从这两个宏咱们能够看出在MFC名类中的CRuntimeClass对象构造链接只有类名及基类名的不一样!

  到此,可能会有朋友问:为何要用两个宏,用一个宏不能够代换CRuntimeClass对象构造链接吗?我的认为确定能够,由于宏只是文字代换的游戏而已。但咱们在编程之中,头文件与源文件是分开的,咱们要在头文件头声明变量及方法,在源文件里实具体实现。便是说咱们要在头文件中声明:

public:
static CRuntimeClass classXXX //XXX为类名
virtual CRuntime* GetRuntimeClass() const;

  而后在源文件里实现:

CRuntimeClass* XXX::classXXX={……};
CRuntime* GetRuntimeClass() const;

{ return &XXX:: classXXX;}//这里不能直接返回&classXXX,由于static变量是类拥有而不是对象拥有。

  咱们一眼能够看出MFC中的DECLARE_DYNAMIC(类名)宏应该这样定义:

#define DECLARE_DYNAMIC(class_name) public: static CRuntimeClass class##class_name; 
virtual CRuntimeClass* GetRuntimeClass() const;

  其中##为链接符,可让咱们传入的类名前面加上class,不然跟原类同名,你们会知道产生什么后果。

  有了上面的DECLARE_DYNAMIC(类名)宏以后,咱们在头文件里写上一句:

DECLARE_DYNAMIC(XXX)

  宏展开后就有了咱们想要的:

public:
static CRuntimeClass classXXX //XXX为类名
virtual CRuntime* GetRuntimeClass() const;

  对于IMPLEMENT_DYNAMIC(类名,基类名),看来也不值得在这里代换文字了,你们知道它是知道回事,宏展开后为咱们作了什么,再深究真是一点意义都没有!

  有了此链表以后,就像有了一张存放各种型的网,咱们能够垂手可得地RTTI。CObject有一个函数BOOL IsKindOf(const CRuntimeClass* pClass) const;,被它如下全部派生员继承。

  此函数实现以下:

BOOL CObject::IsKindOf(const CRuntimeClass* pClass) const
{
CRuntimeClass* pClassThis=GetRuntimeClass();//得到本身的CRuntimeClass对象指针。
while(pClassThis!=NULL)
{
if(pClassThis==pClass) return TRUE;
pClassThis=pClassThis->m_pBaseClass;//这句最关键,指向本身基类,再回头比较,一直到尽头m_pBaseClass为NULL结束。
}
return FALSE;
}

  说到这里,运行时类型识别(RTTI)算是完成了。写这篇文章的时候,我一直重感冒。我曾一度在想,究竟写这东西是为了什么。由于若是我把这些时间用在别的地方(甚至帮别人打打字),应该有数百元的报酬。

  是什么让“嗜财如命”的我继续写下去?我想,无非是想交几个计算机的朋友而已。计算机是你们公认高科技的东西,但学习它的朋友大多只能默默无闻,外界的朋友也不知道怎么去认识你。程序员更不是“潮流”的东西,更加得不到别人的承认。

  有一件我的认为很典型的事情,有一天,我跟一个朋友到一个单位里面。里面有一个女打字员。朋友看着她熟练的指法,心悦诚服地说:“她的电脑水平比你的又高了一个很高的层次!”,那个女的打字高手亦自豪地说:“我靠电脑为生,电脑水平确定比你(指笔者)的好一点!换着是你,若是以电脑为生,我也不敢说好过你!”。虽然我想声明我是计算机专业的,但我知道没有理解,因此我只得客气地点头。

  选择电脑“潮流”的东西实际是选择了平凡,而选择作程序员就是选择了孤独!幸亏我不是一个专门的程序员,但即便如此,我愿意作大家的朋友,由于我爱大家!

http://www.yesky.com/SoftChannel/72342371928702976/20050228/1915827.shtml

///////////////////////////////
首先,很很差意思的说明,我还正在看C++ language programming,但尚未看到关于RTTI的章节。另外,我也不多使用C++ RTTI的特性。因此对RTTI的理解仅限于本身的摸索和思考。若是不正确,请你们指正。

      RTTI特性是C++语言加入较晚的特性之一。和其余语言(好比JAVA)相比,C++的RTTI能力算是很是差的。这与C++的设计要求应该有重要的关系:性能。没错,性能的因素使得C++的不少地方不能称的上完美,可是也正由于如此,在高级通用语言里面,只有C能和C++的性能能够相提并论。

1:typeid的研究

     在C++中,彷佛与RTTI相关的只有一个东西,就是dynamic_cast,原本我认为typeid是RTTI的一部分,可是个人实验代表,并不是如此。typeid的操做是在编译时期就已经决定的了。下面的代码能够证实:

#include 
#include

class A
{
};

class B:public A
{
};

int main()
{
   A *pa;
   B b,*pb;
   pb = &b;
   pa = pb;
   std::cout<<"Name1:"
        << (typeid(pa).name())
        <<"/tName2:"
        <<(typeid(pb).name())
        <<:endl;< p=""></:ENDL;<>

   std::cout<<"pa == pb:"<< (typeid(pa) == typeid(pb))<<:endl;
   return 0;
}


typeid根本不能判别pa其实是一个B*。换句话说,typeid是以字面意思去解释类型,不要期望它能认出一个void*其实是int*(这个连人也作不到:P)。实际上实用价值不大。

固然,在某些特殊地方,也是可以有些效用的,好比模板。

template 
void test(T t)
{
 if(typeid(t) == typeid(char *))
 {
   // 对char *特殊处理
 }
 //...
}


若是编译器优化的好的话,并不会产生废代码,由于typeid编译时期就能够决定了。

 

2:dynamic_cast

    抱歉如今才讲到正题,我对dynamic_cast第一印象就是,它到底是怎么实现的呢?通过一些思考,我认为最简单的方案就是将信息保存在vtable里,它会占用一个vtalbe表的项目。实验和书籍也证实了这一点。可是就会有一个问题,没有vtable的类怎么办?内建类型怎么办?其实,没有vtable的类,它不须要多态,它根本就不须要RTTI,内建类型也同样。这就是说,dynamic_cast只支持有虚函数的类。并且, dynamic_cast不能进行non_base_class *到 class T*的转换,好比void * --> class T *,由于它没法去正确得到vtable。

     这样,dynamic_cast的意义和使用方法就很清楚了,它是为了支持多态而存在的。它用于实现从基类到派生类的安全转换。同时它也在绝大多数状况下避免了使用static_cast--不安全的类型转换。

3:结论

    C++ 的RTTI机制虽然简单,或者说简陋,可是它使得静态类型转换变得无用了。这也是C++的一个不可缺乏的机制。在将来,若是C++可以提供可选的更强的RTTI机制,就像JAVA里的那样,这种语言能够变得更增强大。固然,到时如何提供不损失性能的 RTTI机制,更是一个值得深刻研究的话题了。

第三章

摘要:

  RTTI(Run-Time Type Identification)是面向对象程序设计中一种重要的技术。现行的C++标准对RTTI已经有了明确的支持。不过在某些状况下出于特殊的开发须要,咱们须要本身编码来实现。本文介绍了一些关于RTTI的基础知识及其原理和实现。

RTTI需求:

  和不少其余语言同样,C++是一种静态类型语言。其数据类型是在编译期就肯定的,不能在运行时更改。然而因为面向对象程序设计中多态性的要求,C++中的指针或引用(Reference)自己的类型,可能与它实际表明(指向或引用)的类型并不一致。有时咱们须要将一个多态指针转换为其实际指向对象的类型,就须要知道运行时的类型信息,这就产生了运行时类型识别的要求。

  C++对RTTI的支持:

  C++提供了两个关键字typeid和dynamic_cast和一个type_info类来支持RTTI:

  dynamic_cast操做符:它容许在运行时刻进行类型转换,从而使程序可以在一个类层次结构安全地转换类型。dynamic_cast提供了两种转换方式,把基类指针转换成派生类指针,或者把指向基类的左值转换成派生类的引用。见下例讲述:

void company::payroll(employee *pe) {
//对指针转换失败,dynamic_cast返回NULL
if(programmer *pm=dynamic_cast(pe)){
pm->bonus(); 
}
}
void company::payroll(employee &re) {
try{
//对引用转换失败的话,则会以抛出异常来报告错误
programmer &rm=dynamic_cast(re);
pm->bonus();
}
catch(std::bad_cast){

}
}


  这里bonus是programmer的成员函数,基类employee不具有这个特性。因此咱们必须使用安全的由基类到派生类类型转换,识别出programmer指针。

  typeid操做符:它指出指针或引用指向的对象的实际派生类型。

  例如:

employee* pe=new manager;
typeid(*pe)==typeid(manager) //true


  typeid能够用于做用于各类类型名,对象和内置基本数据类型的实例、指针或者引用,看成用于指针和引用将返回它实际指向对象的类型信息。typeid的返回是type_info类型。

  type_info类:这个类的确切定义是与编译器实现相关的,下面是《C++ Primer》中给出的定义(参考资料[2]中谈到编译器必须提供的最小信息量):

class type_info {
private:
type_info(const type_info&);
type_info& operator=( const type_info& );
public:
virtual ~type_info();
int operator==( const type_info& ) const;
int operator!=( const type_info& ) const;
const char* name() const;
};

实现目标:

  实现的方案

  方案一:利用多态来取得指针或应用的实际类型信息

  这是一个最简单的方法,也是做者目前所采用的办法。

  实现:

enum ClassType{
UObjectClass,
URectViewClass,
UDialogClass,
……
};
class UObject{
virtual char* GetClassName() const {
return "UObject";
};
virtual ClassType TypeOfClass(){
return UObjectClass;
};
};
class UDialog{
virtual char* GetClassName() const {
return "UDialog";
};
virtual ClassType TypeOfClass(){
return UDialogClass;
};
};


  示例:

UObject po=new UObject;
UObject pr=new URectView;
UObject pd=new UDialog;
cout << "po is a " << po->GetClassName() << endl;
cout << "pr is a " << pr->GetClassName() << endl;
cout << "pd is a " << pd->GetClassName() << endl;
cout<TypeOfClass()==UObjectClass< cout<TypeOfClass()==URectViewClass<
cout<TypeOfClass()==UDialogClass<<ENDL;
cout<TypeOfClass()==UObjectClass<<ENDL;
cout<TypeOfClass()==UDialogClass<<ENDL;< td>


  输出:

po is a UObjectClass
pr is a URectViewClass
pd is a UDialogClass
true
true
true
false
false


  这种实现方法也就是在基类中提供一个多态的方法,这个方法返回一个类型信息。这样咱们可以知道一个指针所指向对象的具体类型,能够知足一些简单的要求。

  可是很显然,这样的方法只实现了typeid的部分功能,还存在不少缺点:

  一、 用户每增长一个类必须覆盖GetClassName和TypeOfClass两个方法,若是忘了,会致使程序错误。

  二、 这里的类名和类标识信息不足以实现dynamic_cast的功能,从这个意义上而言此方案根本不能称为RTTI。

  三、 用户必须手工维护每一个类的类名与标识,这限制了以库的方式提供给用户的可能。

  四、 用户必须手工添加GetClassName和TypeOfClass两个方法,使用并不方便。

  其中上面的部分问题咱们能够采用C/C++中的宏技巧(Macro Magic)来解决,这个能够在咱们的最终解决方案的代码中看到。下面采用方案二中将予以解决上述问题。

方案二:以一个类型表来存储类型信息

  这种方法考虑使用一个类结构,除了保留原有的整型类ID,类名字符串外,增长了一个指向基类TypeInfo成员的指针。

struct TypeInfo
{
char* className;
int type_id;
TypeInfo* pBaseClass;
operator== (const TypeInfo& info){
return this==&info;
}
operator!= (const TypeInfo& info){
return this!=&info;
}
};


  从这里能够看到,以这种方式实现的RTTI不支持多重继承。所幸多重继承在程序设计中并不是必须,并且也不推荐。下面的代码中,我将为DP9900软件项目组中类层次结构中的几个类添加RTTI功能。DP9900项目中,绝大部分的类都以单继承方式从UObject这个根类直接或间接继承而来。这样咱们就能够从UObject开始,加入咱们RTTI支持所须要的数据和方法。

class UObject
{
public:
bool IsKindOf(TypeInfo& cls); //判别某个对象是否属于某一个类
public:
virtual int GetTypeID(){return rttiTypeInfo.type_id;}
virtual char* GetTypeName(){return rttiTypeInfo.className;}
virtual TypeInfo& GetTypeInfo(){return rttiTypeInfo;}
static TypeInfo& GetTypeInfoClass(){return rttiTypeInfo;}
private:
static TypeInfo rttiTypeInfo; 
};
//依次为className、type_id、pBaseClass赋值
TypeInfo UObject::rttiTypeInfo={"UObject",0,NULL};


  考虑从UObject将这个TypeInfo类做为每个新增类的静态成员,这样一个类的全部对象将共享TypeInfo的惟一实例。咱们但愿可以在程序运行以前就为type_id,className作好初始化,并让pBaseClass指向基类的这个TypeInfo。

  每一个类的TypeInfo成员约定使用rttiTypeInfo的命名,为了不命名冲突,咱们将其做为private成员。有了基类的支持并不够,当用户须要RTTI支持,还须要本身来作一些事情:

  一、 派生类须要从UObject继承。

  二、 添加rttiTypeInfo变量。

  三、 在类外正确初始化rttiTypeInfo静态成员。

  四、 覆盖GetTypeID、GetTypeName、GetTypeInfo、GetTypeInfoClass四个成员函数。

  以下所示:

class UView:public UObject
{
public:
virtual int GetTypeID(){return rttiTypeInfo.type_id;} 
virtual char* GetTypeName(){return rttiTypeInfo.className;} 
virtual TypeInfo& GetTypeInfo(){return rttiTypeInfo;} 
static TypeInfo& GetTypeInfoClass(){return rttiTypeInfo;} 
private: 
static TypeInfo rttiTypeInfo; 
};


  有了前三步,这样咱们就能够获得一个不算太复杂的链表――这是一棵类型信息构成的"树",与数据结构中的树的惟一差异就是其指针方向相反。

  这样,从任何一个UObject的子类,顺着pBaseClass往上找,总能遍历它的全部父类,最终到达UObject。

  在这个链表的基础上,要判别某个对象是否属于某一个类就很简单。下面给出UObject::IsKindOf()的实现。

bool UObject::IsKindOf(TypeInfo& cls)
{
TypeInfo* p=&(this->GetTypeInfo());
while(p!=NULL){
if(p->type_id==cls.type_id)
return true;
p=p->pBaseClass;
}
return false;
}


  有了IsKindOf的支持,dynamic_cast的功能也就能够用一个简单的safe_cast来实现:

template 
inline T* safe_cast(UObject* ptr,TypeInfo& cls)
{
return (ptr->IsKindOf(cls)?(T*)ptr:NULL);
}


  至此,咱们已经可以从功能上完成前面的目标了,不过用户要使用这个类库的RTTI功能还很麻烦,要敲入一大堆对他们毫无心义的函数代码,要在初始化rttiTypeInfo静态成员时手工设置类ID与类名。其实这些麻烦彻底没必要交给咱们的用户,适当采用一些宏技巧(Macro Magic),就可让C++的预处理器来替咱们写不少枯燥的代码。关于宏不是本文的重点,你能够从最终代码清单看到它们。下面再谈谈关于类ID的问题。

  类ID

  为了使不一样类型的对象可区分,用一个给每一个TypeInfo对象一个类ID来做为比较的依据是必要的。
其实对于咱们这里的需求和实现方法而言,其实类ID并非必须的。每个支持RTTI的类都包含了一个静态TypeInfo对象,这个对象的地址就是在进程中全局惟一。但考虑到其余一些技术如:动态对象建立、对象序列化等,它们可能会要求RTTI给出一个静态不变的ID。在本文的实现中,对此做了有益的尝试。

  首先声明一个用来产生递增类ID的全局变量。再声明以下一个结构,没有数据成员,只有一个构造函数用于初始化TypeInfo的类ID:

extern int TypeInfoOrder=0;
struct InitTypeInfo
{
InitTypeInfo(TypeInfo* info)
{
info->type_id=TypeInfoOrder++;
}
};


  为UObject添加一个private的静态成员及其初始化:

class UObject
{
//……
private:
static InitTypeInfo initClassInfo;
};
InitTypeInfo UObject::initClassInfo(&(UObject::rttiTypeInfo));


  而且对每个从UObject派生的子类也进行一样的添加。这样您将看到,在C++主函数执行前,启动代码将替咱们调用每个类的initClassInfo成员的构造函数InitTypeInfo::InitTypeInfo(TypeInfo* info),而正是这个函数替咱们产生并设置了类ID。InitTypeInfo的构造函数还能够替咱们作其余一些有用的初始化工做,好比将全部的TypeInfo信息登陆到一个表格里,让咱们能够很方便的遍历它。

  但实践与查阅资料让咱们发现,因为C++中对静态成员初始化的顺序没有明确的规定,因此这样的方式产生出来的类ID并不是彻底静态,换一个编译器编译执行产生的结果可能彻底不一样。

  还有一个能够考虑的方案是采用某种无冲突HASH算法,将类名转换成为一个惟一整数。使用标准CRC32算法从类型名计算出一个整数做为类ID也许是个不错的想法[3]。

  程序清单

// URtti.h 
#ifndef __URTTI_H__
#define __URTTI_H__

class UObject;

struct TypeInfo
{
char* className;
int type_id;
TypeInfo* pBaseClass;
operator== (const TypeInfo& info){
return this==&info;
}
operator!= (const TypeInfo& info){
return this!=&info;
}
};

inline std::ostream& operator<< (std::ostream& os,TypeInfo& info)
{
return (os<< "[" << &info << "]" << "/t"
<< info.type_id << ":"
<< info.className << ":"
<< info.pBaseClass << std::endl);
}

extern int TypeInfoOrder;

struct InitTypeInfo
{
InitTypeInfo(/*TypeInfo* base,*/TypeInfo* info)
{
info->type_id=TypeInfoOrder++;
}
};

#define TYPEINFO_OF_CLASS(class_name) (class_name::GetTypeInfoClass())
#define TYPEINFO_OF_OBJ(obj_name) (obj_name.GetTypeInfo())
#define TYPEINFO_OF_PTR(ptr_name) (ptr_name->GetTypeInfo())

#define DECLARE_TYPEINFO(class_name) /
public: /
virtual int GetTypeID(){return TYPEINFO_MEMBER(class_name).type_id;} /
virtual char* GetTypeName(){return TYPEINFO_MEMBER(class_name).className;} /
virtual TypeInfo& GetTypeInfo(){return TYPEINFO_MEMBER(class_name);} /
static TypeInfo& GetTypeInfoClass(){return TYPEINFO_MEMBER(class_name);} /
private: /
static TypeInfo TYPEINFO_MEMBER(class_name); /
static InitTypeInfo initClassInfo; /

#define IMPLEMENT_TYPEINFO(class_name,base_name) /
TypeInfo class_name::TYPEINFO_MEMBER(class_name)= /
{#class_name,0,&(base_name::GetTypeInfoClass())}; /
InitTypeInfo class_name::initClassInfo(&(class_name::TYPEINFO_MEMBER(class_name)));

#define DYNAMIC_CAST(object_ptr,class_name) /
safe_cast(object_ptr,TYPEINFO_OF_CLASS(class_name))

#define TYPEINFO_MEMBER(class_name) rttiTypeInfo

class UObject
{
public:
bool IsKindOf(TypeInfo& cls);
public:
virtual int GetTypeID(){return TYPEINFO_MEMBER(UObject).type_id;}
virtual char* GetTypeName(){return TYPEINFO_MEMBER(UObject).className;}
virtual TypeInfo& GetTypeInfo(){return TYPEINFO_MEMBER(UObject);}
static TypeInfo& GetTypeInfoClass(){return TYPEINFO_MEMBER(UObject);}
private:
static TypeInfo TYPEINFO_MEMBER(UObject);
static InitTypeInfo initClassInfo;
};

template 
inline T* safe_cast(UObject* ptr,TypeInfo& cls)
{
return (ptr->IsKindOf(cls)?(T*)ptr:NULL);
}
#endif
// URtti.cpp 
#include "urtti.h"

extern int TypeInfoOrder=0;

TypeInfo UObject::TYPEINFO_MEMBER(UObject)={"UObject",0,NULL};
InitTypeInfo UObject::initClassInfo(&(UObject::TYPEINFO_MEMBER(UObject)));

bool UObject::IsKindOf(TypeInfo& cls)
{
TypeInfo* p=&(this->GetTypeInfo());
while(p!=NULL){
if(p->type_id==cls.type_id)
return true;
p=p->pBaseClass;
}
return false;
}
// mail.cpp 
#include 
#include "urtti.h"
using namespace std;

class UView:public UObject
{
DECLARE_TYPEINFO(UView)
};
IMPLEMENT_TYPEINFO(UView,UObject)

class UGraph:public UObject
{
DECLARE_TYPEINFO(UGraph)
};
IMPLEMENT_TYPEINFO(UGraph,UObject)

void main()
{
UObject* po=new UObject;
UView* pv=new UView;
UObject* pg=new UGraph;
if(DYNAMIC_CAST(po,UView)) 
cout << "po => UView succeed" << std::endl;
else
cout << "po => UView failed" << std::endl;
if(DYNAMIC_CAST(pv,UView))
cout << "pv => UView succeed" << std::endl;
else
cout << "pv => UView failed" << std::endl;
if(DYNAMIC_CAST(po,UGraph)) 
cout << "po => UGraph succeed" << std::endl;
else
cout << "po => UGraph failed" << std::endl;
if(DYNAMIC_CAST(pg,UGraph))
cout << "pg => UGraph succeed" << std::endl;
else
cout << "pg => UGraph failed" << std::endl;
}


  实现结果

  本文实现了以下几个宏来支持RTTI,它们的使用方法均可以在上面的代码中找到:
  

宏函数

功能及参数说明

DECLARE_TYPEINFO(class_name)

为类添加RTTI功能放在类声明的起始位置

IMPLEMENT_TYPEINFO(class_name,base)

同上,放在类定义任何位置

TYPEINFO_OF_CLASS(class_name)

至关于typeid(类名)

TYPEINFO_OF_OBJ(obj_name)

至关于typeid(对象)

TYPEINFO_OF_PTR(ptr_name)

至关于typeid(指针)

DYNAMIC_CAST(object_ptr,class_name)

至关于dynamic_castobject_ptr

性能测试

  测试代码:

  这里使用相同次数的DYNAMIC_CAST和dynamic_cast进行对比测试,在VC6.0下编译运行,使用默认的Release编译配置选项。为了不编译器优化致使的不公平测试结果,我在循环中加入了无心义的计数操做。

void main()
{
UObject* po=new UObject;
UView* pv=new UView;
UObject* pg=new UGraph;
int a,b,c,d;
a=b=c=d=0;
const int times=30000000;
cerr << "时间测试输出:" << endl;
cerr << "start my DYNAMIC_CAST at: " << time(NULL) << endl;
for(int i=0;i<TIMES;I++){
if(DYNAMIC_CAST(po,UView)) a++; else a--;
if(DYNAMIC_CAST(pv,UView)) b++; else b--;
if(DYNAMIC_CAST(po,UGraph)) c++; else c--;
if(DYNAMIC_CAST(pg,UGraph)) d++; else d--;
}
cerr << "end my DYNAMIC_CAST at: " << time(NULL) << endl;
cerr << "start c++ dynamic_cast at: " << time(NULL) << endl;
for(i=0;i<TIMES;I++){
if(dynamic_cast(po)) a++; else a--;
if(dynamic_cast(pv)) b++; else b--;
if(dynamic_cast(po)) c++; else c--;
if(dynamic_cast(pg)) d++; else d--;
}
cerr << "end c++ dynamic_cast at: " << time(NULL) << endl;
cerr << a << b << c << d << endl;
}


  运行结果:

start my DYNAMIC_CAST at: 1021512140
end my DYNAMIC_CAST at: 1021512145
start c++ dynamic_cast at: 1021512145
end c++ dynamic_cast at: 1021512160


  这是上述条件下的测试输出,咱们能够看到,本文实现的这个精简RTTI方案运行DYNAMIC_CAST的时间开销只有dynamic_cast的1/3。为了获得更全面的数据,还进行了DEBUG编译配置选项下的测试。

  输出:

start my DYNAMIC_CAST at: 1021512041
end my DYNAMIC_CAST at: 1021512044
start c++ dynamic_cast at: 1021512044
end c++ dynamic_cast at: 1021512059


  这种状况下DYNAMIC_CAST运行速度要比dynamic_cast慢一倍左右。若是在Release编译配置选项下将UObject::IsKindOf方法改为以下inline函数,咱们将获得更让人兴奋的结果(DYNAMIC_CAST运行时间只有dynamic_cast的1/5)。

inline bool UObject::IsKindOf(TypeInfo& cls)
{
for(TypeInfo* p=&(this->GetTypeInfo());p!=NULL;p=p->pBaseClass)
if(p==&cls) return true;
return false;
}


  输出:

start my DYNAMIC_CAST at: 1021512041
end my DYNAMIC_CAST at: 1021512044
start c++ dynamic_cast at: 1021512044
end c++ dynamic_cast at: 1021512059


  结论:

  由本文的实践能够得出结论,本身动手编码实现RTTI是简单可行的。这样的实现能够在编译器优秀的代码优化中表现出比dynamic_cast更好的性能,并且没有带来过多的存储开销。本文的RTTI以性能为主要设计目标,在实现上必定程度上受到了MFC的影响。适于嵌入式环境。

第三章

连接指示符extern C
若是程序员但愿调用其余程序设计语言尤为是C 写的函数,那么调用函数时必须
告诉编译器使用不一样的要求,例如当这样的函数被调用时,函数名或参数排列的顺序可能
不一样,不管是C++函数调用它仍是用其余语言写的函数调用它
程序员用连接指示符linkage directive 告诉编译器该函数是用其余的程序设计语言
编写的,连接指示符有两种形式:既能够是单一语句single statement 形式。也能够是复
合语句compound statement 形式
// 单一语句形式的连接指示符
extern "C" void exit(int);
// 复合语句形式的连接指示符
extern "C" {
int printf( const char* ... );
int scanf( const char* ... );
}
// 复合语句形式的连接指示符
extern "C" {
#include <cmath>
}
连接指示符的第一种形式由关键字extern 后跟一个字符串常量以及一个普通的函数
声明构成,虽然函数是用另一种语言编写的,但调用它仍然须要类型检查,例如编译器
会检查传递给函数exit()的实参的类型是不是int ,或者可以隐式地转换成int 型
多个函数声明能够用花括号包含在连接指示符复合语句中,这是连接指示符的第二种形
式,花招号被用做分割符表示连接指示符应用在哪些声明上,在其余意义上该花括号被忽
略,因此在花括号中声明的函数名对外是可见的就好像函数是在复合语句外声明的同样
例如在前面的例子中复合语句extern "C"表示函数printf()和scanf()是在C 语言中写的,
函数所以这个声明的意义就如同printf()和scanf()是在extern "C"复合语句外面声明的
同样,
当复合语句连接指示符的括号中含有#include 时在头文件中的函数声明都被假定是用
连接指示符的程序设计语言所写的在前面的例子中在头文件<cmath>中声明的函数都是C
函数
连接指示符不能出如今函数体中下列代码段将会致使编译错误
int main()
{
// 错误: 连接指示符不能出如今函数内
extern "C" double sqrt( double );
305 第七章函数
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
若是把连接指示符移到函数体外程序编译将无错误
extern "C" double sqrt( double );
int main()
{
double getValue(); //ok
double result = sqrt ( getValue() );
//...
return 0;
}
可是把连接指示符放在头文件中更合适,在那里函数声明描述了函数的接口所属
若是咱们但愿C++函数可以为C 程序所用又该怎么办呢?咱们也可使用extern "C"
连接指示符来使C++函数为C 程序可用例如:
// 函数calc() 能够被C 程序调用
extern "C" double calc( double dparm ) { /* ... */ }
若是一个函数在同一文件中不仅被声明一次,则连接指示符能够出如今每一个声明中,它
也能够只出如今函数的第一次声明中在这种状况下第二个及之后的声明都接受第一个声
明中连接指示符指定的连接规则例如
// ---- myMath.h ----
extern "C" double calc( double );
// ---- myMath.C ----
// 在Math.h 中的calc() 的声明
#include "myMath.h"
// 定义了extern "C" calc() 函数
// calc() 能够从C 程序中被调用
double calc( double dparm ) { // ...
在本节中咱们只看到为C 语言提供的连接指示extern "C", extern "C"是唯一被
保证由全部C++实现都支持的每一个编译器实现均可觉得其环境下经常使用的语言提供其余连接
指示,例如:extern "Ada"能够用来声明是用Ada 语言写的函数,extern "FORTRAN"用来
声明是用FORTRAN 语言写的函数等等,由于其余的连接指示随着具体实现的不一样而不一样
因此建议读者查看编译器的用户指南以得到其余连接指示符的

第四章

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.

For the sake of clarity, this discussion of RTTI is almost completely restricted to pointers. However, the concepts discussed also apply to references.

There are three main C++ language elements to run-time type information:

Used for conversion of polymorphic types.

Used for identifying the exact type of an object.

Used to hold the type information returned by the typeid operator.

Visual C++ Compiler Options

/GR (Enable Run-Time Type Information)

This option (/GR) adds code to check object types at run time.
When this option is specified, the compiler defines the _CPPRTTI preprocessor macro. The option is cleared (/GR–) by default.

To set this compiler option in the Visual Studio development environment 
   1. Open the project's Property Pages dialog box. For details, 
         see Setting Visual C++  Project Properties. 
   2. Click the C/C++ folder. 
   3. Click the Language property page. 
   4. Modify the Enable Run-Time Type Info property. 

典型的RTTI是经过在VTable中放一个额外的指针来实现的。这个指针指向一个描述该特定类型的
typeinfo结构(每一个新类只产生一个typeinfo的实例),因此typeid()表达式的做用实际上很简单。
VPtr用来取typeinfo的指针,而后产生一个结果typeinfo结构的一个引用,而后调用库中的一个例程判
断源typeinfo是否与目标typeinfo相同或者是目标typeinfo的派生类。

MFC的RTTI主要是经过CRuntimeClass实现的。咱们的类只需从CObject派生,并分别在头文件和实
现文件中声明DECLARE_DYNAMIC/IMPLEMENT_DYNAMIC或
DECLARE_DYNCREATE/IMPLEMENT_DYNCREATE或
DECLARE_SERIAL/IMPLEMENT_SERIAL 宏就可拥有RTTI。

CRuntimeClass is a structure and therefore does not have a base class.

CRuntimeClass is a structure you can use to obtain information about an object or its 
base class at run time. The ability to determine the class of an object at run time is 
useful when extra type checking of function arguments is needed, or when you must 
write special-purpose code based on the class of an object.

Each class derived from CObject is associated with a CRuntimeClass structure that you can use to obtain information about an object or its base class at run time. Run-time 
class information is not supported directly by the C++ language.

CRuntimeClass provides information on the related C++ object, such as a pointer to 
the CRuntimeClass of the base class and the ASCII class name of the related class. 
This structure also implements various functions that can be used to dynamically create 
objects, specifying the type of object by using a familiar name, and determining if the 
related class is derived from a specific class.

struct CRuntimeClass {
 LPCSTR m_lpszClassName;
 int m_nObjectSize;
 UINT m_wSchema CObject* (PASCAL* m_pfnCreateObject)( );
 CRuntimeClass* (PASCAL* m_pfnGetBaseClass)( );
 CRuntimeClass* m_pBaseClass;
 CObject* CreateObject( );
 BOOL IsDerivedFrom(const CRuntimeClass* pBaseClass) const;
 void Store(CArchive& ar) const;
 static CRuntimeClass* PASCAL Load( Carchive& ar, UINT* pwSchemaNum);
 CRuntimeClass* m_pNextClass;
 };

类比:   1. CObject::GetRuntimeClass() <==> typeid(object)   2. RUNTIME_CLASS(classname) <==> typeid(classname)   3. CObject::IsKindOf(RUNTIME_CLASS(classname))          <==>       typeid(object) == typeid (classname)   4. CRuntimeClass::IsDeriveFrom(RUNTIME_CLALL(classname))         <==>       dynamic_cast<classname*>(ptr) != NULL   在编写MFC程序的时候,最好不要用c++ RTTI以避免增长开销。由于他们的实现各自独立并且MFC的CRuntime实现的功能是RTTI的超集,由于CRuntime还支持动态建立,于是也支持序列化。

相关文章
相关标签/搜索