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

2 thoughts on “C++中的RTTI架构

    • Author gravatar

      I actually wanted to post a small remark to express gratitude to you for these nice suggestions you are posting at this website. My rather long internet investigation has finally been paid with good quality ideas to talk about with my partners. I would mention that many of us visitors are rather lucky to dwell in a decent community with very many wonderful professionals with beneficial advice. I feel rather blessed to have seen your website page and look forward to tons of more brilliant minutes reading here. Thanks once more for a lot of things.

    • Author gravatar

      I precisely wanted to thank you very much yet again. I do not know the things I might have implemented in the absence of the type of recommendations revealed by you relating to my topic. It was before a very terrifying situation in my view, however , being able to see your specialised technique you processed it forced me to jump with contentment. Now i’m grateful for this help and even pray you are aware of an amazing job your are getting into training other individuals via your web site. Probably you haven’t come across any of us.

发表评论

电子邮件地址不会被公开。