C++中的RTTI架构



  在 C++ 中,默认提供的运行时类型信息(Run-Time Type Information, RTTI)主要有三个目的:

  • 支持 typeid 操作符
  • 匹配以及捕获抛出的异常(异常分配器)
  • 实现 dynamic_cast 操作符


  dynamic_cast 只关注于继承多态的相关转换操作,而 typeid 以及 异常 则可以对所有类型使用。抛出的异常可能是各种类型的,所以需要有运行时动态地捕获;比如抛出一个 int * 类型的异常,可以使用 const int * 来进行捕获。
  RTTI 允许程序在运行期间确定一个对象的类型。

type_info

  type_info 是表示为类型信息的类,其底层实际上是保存了类的字符串名字,而运行时类的字符串名称就是实现反射的核心
  通过使用 typeid 关键字可以获得指定类的 type_info 的常引用,而 type_info 为全局常量量,直到程序结束才会释放。
  如果碰到需要将类型信息为 key 插入容器的情况,可以使用 type_index。type_index 已经原生支持 STL 的所有容器。

dynamic_cast

  对于非继承的类型,类型信息在运行期和编译期都保持一致,甚至可以直接替换到代码中;对于继承体系中的 RTTI,多态的转型就必须要依赖于虚函数和类型结构的具体实现。
  dynamic_cast 是一个实现向下转型的检查;它在运行时判断两个类型之间的转换是否有效。如果检查结果有效,就使用 static_cast 强转为指定类型指针;对于指针,检查失败就返回空指针,对于引用,检查失败抛出 bad_cast 异常。

class Base
{
public:
    virtual ~Base(void) {}
};
class D1 : public Base {};
class D2 : public Base {};

int main()
{
    Base* b = new D1;
    std::cout << "Base is D1 actually? "
        << (dynamic_cast<D1*>(b) ? "true" : "false")
        << std::endl;

    std::cout
        << "Base is D2 actually? "
        << (dynamic_cast<D2*>(b) ? "true" : "false")
        << std::endl;
    delete b;
}
// Base is D1 actually? true
// Base is D2 actually? false

RTTI架构

  对于 C++ API 的实际使用不再赘述。
  静态类型的信息在编译期就可以完全确定,也就不需要 RTTI 帮助。对于需要在运行时获取信息的多态类型,通常与虚表有关,但又不完全是虚表。

虚表的内存分布

  C++ 的每一个类对应的虚表结构如下图所示:(没错,本文的目的就是为了放这张图)


RTTI layout

  • 有虚函数的类实例第一个指针为虚函数指针,它会指向定义了虚表的第一个字节位置。
  • 虚表是只读的,而且其实虚表结构还有一个头部,称为 vtable_prefix。所以在获取类型信息的时候需要将指针前移。

vtable_prefix

  下面是 gcc 在 tinfo.h 中定义的 vtable_prefix:

// Initial part of a vtable, this structure is used with offsetof, so we don't
// have to keep alignments consistent manually.
struct vtable_prefix 
{
  // Offset to most derived object.
  ptrdiff_t whole_object;
  // Pointer to most derived type_info.
  const __class_type_info *whole_type; 
  // What a class's vptr points to.
  const void *origin;               
};

  • whole_object 表示当前指针指向对象的偏移量。通常为0,但指针指向的是多继承的非第一个类的地址时,则为一个负数,表示偏移多少字节。
  • whole_type 指向 C++ 的挤成类型:class(基类)、si_class(单一继承类型)、vmi_class(多重或虚拟继承类型)
  • origin 表示虚函数表的入口,等于实例的虚指针。origin 在这里的作用是 offsetof,反向获取 whole_object 的指针。
  const vtable_prefix *prefix =
      adjust_pointer <vtable_prefix> (vtable, 
                      -offsetof (vtable_prefix, origin));

  关于各种继承模式下的内存分布不再赘述,搜索即可得到。


  type_info就是 RTTI 存储信息的地方。多重继承的类虽然有多个虚表指针,但是仍然只会有 一个 type_info 指针,也就保证除了菱形继承,所有的 dynamic_cast 都可以成功。甚至是,由中部指针推导出兄弟类型的指针。

About the Author

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注