当前位置:网站首页 > 技术博客 > 正文

rtt算法



相关系列文章

C++无锁队列的原理与实现

如何写出高质量的函数?快来学习这些coding技巧

从C++容器中获取存储数据的类型

C++之多层 if-else-if 结构优化(一)

C++之多层 if-else-if 结构优化(二)

C++之多层 if-else-if 结构优化(三)

C++之Pimpl惯用法

C++之RTTI实现原理

目录

1.引言

2.typeid

2.1.虚函数表(vtable)

2.2.类型信息(type_info)

3.dynamic_cast

4.缺陷

5.一些库/软件提供的RTTI实现

5.1. CATIA的RTTI

5.2. QT的RTTI

5.3. FreeCAD的RTTI

6.实例

7.总结


        RTTI是Runtime Type Identification的缩写,意思是运行时类型识别。C++引入这个机制是为了让程序在运行时能根据基类的指针或引用来获得该指针或引用所指的对象的实际类型。但是现在RTTI的类型识别已经不限于此了,它还能通过typeid操作符识别出所有的基本类型(int,指针等)的变量对应的类型。

        C++提供了typeiddynamic_cast两个运算符(而非函数)关键字来提供动态类型信息和动态类型转换,使用需要在编译器选项中指定-rtti(clang和gcc等都默认开启)。关闭则可以设置选项仅当指定了 /GR(启用运行时类型信息)编译选项时,才会为多态类生成类型信息。

        typeid运算符,该运算符返回其表达式或类型名的实际类型。即返回一个类型为std::type_info的对象的const引用。type_info是std中的一个类,它用于记录与类型相关的信息。类type_info的定义大概如下:

 
   

        从上面的定义也可以看到,type_info提供了两个对象的相等比较操作,但是用户并不能自己定义一个type_info的对象,而只能通过typeid运算符返回一个对象的const引用来使用type_info的对象。因为其只声明了一个构造函数(复制构造函数)且为delete,所以编译器不会合成任何的构造函数,而且赋值操作运行符也为delete。这两个操作就完全禁止了用户对type_info对象的定义和复制操作,用户只能通过指向type_info的对象的指针或引用来使用该类。

        C++标准并不涉及具体实现的细节,而更侧重于定义语言的语法和语义。因此typeid的性能就由具体编译器实现所决定。然而,一般而言,typeid的实现通常基于虚函数表(vtable)和类型信息(type_info)。以下是typeid的典型实现方式:

        对于含有虚函数的类,每个对象都包含一个指向虚函数表的指针。虚函数表中存储了该类及其所有基类的虚函数的地址。typeid可以通过访问这个虚函数表,找到存储类型信息的部分。

        每个类的type_info对象包含有关该类的类型信息,例如类名等。这个信息通常被存储在只读数据区域。type_info的实现可能包含了一个字符串或其他标识符,以表示该类型。

        typeid运算符用于获取对象的类型信息。它返回一个type_info对象,包含有关类型的信息,如类型名称。

        具体示例如下(vs2019环境下编译生成结果):

 
   

输出:

 
   

        从输出的结果可以看出,无论printTypeInfo函数中指针pm指向的对象是基类CM的对象,还是指向派生类CMM的对象,typeid运行返回的pm的类型信息都是相同的,因为pm为一个静态类型,其类型名均为class CM const *__ptr64。但是typeid运算符却能正确地计算出了pm指向的对象的实际类型,分别为CM和CMM。

        那么typeid是如何推理出这个类型信息的呢?多态类的对象的类型信息保存在虚函数表的索引的-1的项中,该项是一个type_info对象的地址,该type_info对象保存着该对象对应的类型信息,每个类都对应着一个type_info对象,如下图所示:

9432f5b0dd3143aeb2d6756ef3010896.png

296ef5e17eb94adbb9e5ec019d7de223.png

      代码如下:

 
   

        对于向下转换编译器则有真正的工作要做:例如对以下的继承链来说,将一个原本是C类型的对象的A*指针转换为C* 指针也很快,只需要检查一下type_info结构体是否相同,但如果要转换成其他类型则需要遍历树中A到指定类型的所有路径。

6e2f0d3481e3419aa1237a300411fef2.png

 
   

从上述的结果可以看出在向下转型中,只有dynamic_cast才能实现安全的向下转型。那么dynamic_cast是如何实现的呢?有了上面typeid和虚函数表的知识后,这个问题并不难解释了,以之前的转换为例。

1)计算指针或引用变量所指的对象的虚函数表的type_info信息,如下:

*(type_info*)pB->vptr[-1]

2)静态推导向下转型的目标类型的type_info信息,即获取类CMM的type_info信息

3)比较1)和2)中获取到的type_info信息,若2)中的类型信息与1)中的类型信息相等或是其基类类型,则返回相应的对象或子对象的地址,否则返回NULL。

引用的情况与指针稍有不同,失败时并不是返回NULL,而是抛出一个bad_cast异常,因为引用不能参考NULL。

        由于 typeid 和 dynamic_cast 都是由编译器自己实现的, 所以性能没有统一的标准。同时,我们使用RTTI最多的场景可能是dynamic_cast来保证down cast的类型安全。但众所周知的是,dynamic_cast需要从虚表中查询类型信息,然后对比type_info,这个操作本身首先就很慢。
        dynamic_cast在很多情况下需要动态遍历继承树,并且一条条比对type_info中的类型元信息,在有的编译器中该比对被实现为字符串比较,效率更为低下。如果dynamic_cast使用得较多,则性能开销不小。
        此外,在使用上还存在一些问题:
a) 由于编译器可以开关默认的RTTI设置, 所以在多动态库使用场景中, 必须要求所有动态库都开启该选项。这就对外部人员进行二次开发有点强要求了。
b) 其次还会抛异常, 因此将不得不增加额外的异常处理逻辑。



如下是CATIA提供的RTTI实现,其中,虚方法IsA和IsAKindOf就是在运行时断言类型的。当明确类型时,就可以直接static_cast到特定的类型。其实可以发现,CATIA提供的实现类或者接口,都会通过CATDeclareClass或者CATDeclareInterface来进行声明。两个宏都会与CATMetaClass关联,在CATMetaClass中会有更多有关RTTI的实现细节。

 
   

在Qt中,Q_OBJECT宏的使用会触发Qt元对象系统(QMetaObject System),从而实现了一种类似于RTTI的机制。下面是一个简单的例子,演示了如何在Qt中使用Q_OBJECT宏和元对象系统:

 
   

        FreeCAD中的RTTI系统也是通过宏来定义相关的虚函数与实现。如下DocumentObject类中声明的宏PROPERTY_HEADER_WITH_OVERRIDE,那么在CPP中会定义相关的实现,这时通过宏PROPERTY_SOURCE(DocumentObject, TransactionalObject)来实现。此外TYPESYSTEM_HEADER_WITH_OVERRIDE也是宏之一。

 
   

事实上,自定义实现RTTI可以提供更灵活和高效的解决方案。以下是一个简单的自定义实现RTTI的示例。主要是通过getName虚方法在运行时断言类型。

 
   

        C++的RTTI为程序员提供了在运行时获取类型信息的便利,但在某些情况下,特别是涉及性能要求高的应用中,开发者可能需要权衡使用默认RTTI机制的开销,并考虑是否需要自定义实现以满足特定需求。

        自定义实现RTTI可以提供更灵活和高效的类型信息管理方式。

        我们设计RTTI时,基本上是通过宏的方式载入一些虚函数或者类型来处理一个class,在运行时识别到具体类型,就可以通过static_cast来进行安全转换。

  • 上一篇: udp编程实例
  • 下一篇: jdbc数据库连接池参数
  • 版权声明


    相关文章:

  • udp编程实例2025-06-17 14:30:00
  • 监控jvm工具2025-06-17 14:30:00
  • jvm调优工具及使用2025-06-17 14:30:00
  • libxml xpath2025-06-17 14:30:00
  • 线程同步原理2025-06-17 14:30:00
  • jdbc数据库连接池参数2025-06-17 14:30:00
  • c++类中构造函数2025-06-17 14:30:00
  • 公式编辑器破解方法2025-06-17 14:30:00
  • javajdbc连接池2025-06-17 14:30:00
  • varchar2和nvarchar2的区别2025-06-17 14:30:00