C++ RTTI及“反射”技术

来源:互联网 发布:win7仿linux主题 编辑:程序博客网 时间:2024/04/30 02:08
 

C++ RTTI及“反射”技术

 1531人阅读 评论(1) 收藏 举报
c++文件管理器class编译器filebuild

 http://blog.csdn.net/acdnjjjdjkdckjj/article/details/6326189

RTTI

  RTTI(Run-Time Type Information)运行时类型检查的英文缩写,它提供了运行时确定对象类型的方法。面向对象的编程语言,象C++,Java,delphi都提供了对RTTI的支持。 本文将简略介绍 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 操作时,应该权衡对性能的影响。

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

答:
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(b2);
           cout << "true" << endl;
       } catch( bad_cast )
       {
           cout << "false" << endl;
       }
       try {
           Derive22& rb2 = dynamic_cast(b2);
           cout << "true" << endl;
       } catch( ... ) // 应该是 bad_cast,但不知道为什么在VC++6.0中却不行
       {
           cout << "false" << endl;
       }

       return 0;
}

 

 

 

 

首先,很不好意思的说明,我还正在看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="">

      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机制,更是一个值得深入研究的话题了。

 

 

 

 

   对于没有virtual函数的类而言,typeid是编译时期的事情(也就是静态类型);对于有virtual函数的类而言,typeid是运行时期的事情(也就是动态类型)。   
    
     为什么是这样呢?   
     因为RTTI(typeid和dynamic_cast都是RTTI的一部分)大部分都是要利用virtual      table来实现的,比如在virtual      table的第0个索引处存放指向type_info信息的指针。所以如果没有virtual      function,那么此class就没有virtual      table,所以RTTI也自然就不能正常运行了(那么typeid也就自然不能够正常运行了,而只能根据编译时期的静态类型来进行判断)。

 

C++ 的“反射”技术

Lit Window Library
来自http://sourceforge.net/projects/litwindow 大概看了一下,使用了宏建立了一个比较复杂的数据结构,用来提供成员反查以及成员访问。
tinybind
来自http://sourceforge.net/projects/tinybind/ 这个应该不算是反射,不过通过了定义一个Binding函数实现成员和XML的Element之间的绑定,自动实现了XML<->class,对实现C++的“反射”也有一定的参考作用。
OODBC
来自http://www.garret.ru/~knizhnik/cpp.html 这个也应该不算是反射,也是通过宏建立一个记录成员变量在类中的偏移位置的链表。自动实现数据库字段与成员的绑定,有点Hibernate的味道。呵呵。同时,作者提供的另外几个例如FastDB、GigaBase、goods、POST++都使用了类似的技术。
0 0
原创粉丝点击