在 C++ 中,默认提供的运行时类型信息(Run-Time Type Information, RTTI)主要有三个目的:
dynamic_cast 只关注于继承多态的相关转换操作,而 typeid 以及 异常 则可以对所有类型使用。抛出的异常可能是各种类型的,所以需要有运行时动态地捕获;比如抛出一个 int * 类型的异常,可以使用 const int * 来进行捕获。
RTTI 允许程序在运行期间确定一个对象的类型。
type_info 是表示为类型信息的类,其底层实际上是保存了类的字符串名字,而运行时类的字符串名称就是实现反射的核心。
通过使用 typeid 关键字可以获得指定类的 type_info 的常引用,而 type_info 为全局常量量,直到程序结束才会释放。
如果碰到需要将类型信息为 key 插入容器的情况,可以使用 type_index。type_index 已经原生支持 STL 的所有容器。
对于非继承的类型,类型信息在运行期和编译期都保持一致,甚至可以直接替换到代码中;对于继承体系中的 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
对于 C++ API 的实际使用不再赘述。
静态类型的信息在编译期就可以完全确定,也就不需要 RTTI 帮助。对于需要在运行时获取信息的多态类型,通常与虚表有关,但又不完全是虚表。
C++ 的每一个类对应的虚表结构如下图所示:(没错,本文的目的就是为了放这张图)
下面是 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;
};
const vtable_prefix *prefix =
adjust_pointer <vtable_prefix> (vtable,
-offsetof (vtable_prefix, origin));
关于各种继承模式下的内存分布不再赘述,搜索即可得到。
type_info就是 RTTI 存储信息的地方。多重继承的类虽然有多个虚表指针,但是仍然只会有 一个 type_info 指针,也就保证除了菱形继承,所有的 dynamic_cast 都可以成功。甚至是,由中部指针推导出兄弟类型的指针。