C++ RTTI 机制

来源:互联网 发布:类似photosynth的软件 编辑:程序博客网 时间:2024/06/06 07:11

RTTI(Run Time Type information): 是编译器在 编译期间 生成的特殊类型信息(type_info),包括对象继承关系,对象本身的描述,RTTI 是为多态而生成的信息,所以 只有具有虚函数的对象才会生成 RTTI。

在 C++ 中,有以下两个函数用于 运行时类型检测


一、dynamic_cast


语法:

dynamic_cast < new_type > ( expression )

返回类型为 new_type 的值 或 抛出一个异常。

  • new_type

    new_type 必须是指向完整类类型或 void * 的指针或引用。必须是类的指针或引用,或 void*。因为没有对 void 的引用。

  • expression

    expression 必须是指向完整类类型的指针或引用。而且,expression 所代表的类必须包含虚函数。

如果转换成功,dynamic_cast 将返回一个 new_type 的值。 如果转换失败,而且 new_type 是指针,那么将返回NULL; 而如果 new_type 是引用,那么将抛出异常 std::bad_cast。

1.1 实现原理

在 C++ 对象模型 中 , 我们知道,在包含虚函数的类中,会产生一张虚函数表,而虚函数表的前面,还会有一部分额外的信息,用于记录与类相关的信息 —— type_info,比如说,对象继承关系,对象本身的描述等等。


这里写图片描述

dynamic_cast 能否转换成功,所依据的就是这一部分 type_info 信息。具体可参考 C++ dynamic_cast实现原理

dynamic_cast 的时间和空间代价是相对较高的,在设计时应避免使用。可参考 How expensive is RTTI?

如果整个工程都不需要 dynamic_cast,可以禁用运行时类型信息(vs2008默认是启用的)。

禁用方法如下:

依次选择【工程属性】、【配置属性】、【C/C++】、【语言】。将【启用运行时类型信息】改为”否“。

1.2 应用场景

dynamic_cast 常用于有“父子”关系的两个类对象指针或引用之间进行转换。而且,只能在有虚函数的类层次之间使用 dynamic_cast 。

如下一段代码:

class Interface{public:        virtual void GenericOp() = 0;};class SpecificClass : public Interface{public:        virtual void GenericOp();        virtual void SpecificOp();};

我们定义一个Interface 类型的指针:

Interface * ptr = new SpecificClass; // 可以用基类指针指向派生类对象,因为这是安全的。// 而不能用派生类指针指向基类对象,因为可能调用基类不存在的方法,是不安全的。

ptr 指针指向的实际类型是 SpecificClass 对象,如果我们希望通过 ptr 指针调用 SpecificClass::SpecificOp 方法,为了保证基类指针顺利安全的调用派生类方法。需要使用 dynamic_cast 进行动态类型转换:

SpecificClass* ptr_spec = dynamic_cast < SpecificClass * >(ptr);if( ptr_spec ){    ptr_spec->SpecificOp();}else{    ptr->GenericOp();};

dynamic_cast 是如何保证安全的? dynamic_cast 会对待检测指针进行类型检测,只有 ptr 指向的对象确实是 SpecificClass 类型(或者包含) ,才能成功转换,否则返回 NULL 或者 抛出 std::bad_cast 异常(当转换对象为NULL的引用)。

dynamic_cast 小结:

  • 没有虚函数,不能动态转换。

  • 转换的依据是,type_info 信息。

  • 如果 expression 所指对象的实际类型就是 new_type,则转换成功。 如: 基类指针(假如指向派生类对象),转换为派生类对象时,转换成功。

  • 如果 expression 所指对象的实际类型“包含” new_type,则转换成功。 如:A 派生了 B, B 派生了 C, A * ap = new C; B * bp = dynamic<B*>(ap) ;

  • 如果 expression 和 new_type 之间毫无关系,则转换失败。

  • expression 指向的类一定要包含虚函数,否则编译出错。new_type 只要保证是类的指针或者引用,就不会编译出错。

可以用 typeid 在语义上实现 dynamic_cast :

template<typename T, typename T1>T1 *my_dynamic_cast(T *t){    if(typeid(t) == typeid(T1)){        return (T1*)t;    } else{        return NULL;    }}


二、typeid


函数原型:

type_info & typeid( object );

typeid 的主要作用是获得object变量的类型;它将返回一个type_info 对象的引用,type_info用于描述对象。如果object是对一个NULL指针的解引用,那么将抛出std::bad_typeid异常。

当需要获得一个对象的类信息时,总是选择typeid 而不是 dynamic_cast因为typeid检测一个类型或者非强制转换的值时,只耗费常数时间。而 dynamic_cast 需要在运行时,对对象进行参数推导。

typeid 只对多态类型(该类至少包含一个virtual成员函数)有效,而且参数 object 是指针的解引用(typeid(*ptr) )或者对象引用( typeid(ref) )。因为只有包含虚函数的类指针(或者引用)才需要运行时检测,而其他的类型在编译时便能确定。

#include <iostream>#include <typeinfo>  //for 'typeid' to workclass Person {public:   // ... Person members ...   virtual ~Person() {}};class Employee : public Person {   // ... Employee members ...};int main () {   Person person;   Employee employee;   Person *ptr = &employee;   std::cout << typeid(person).name() << std::endl;   // Person (statically known at compile-time)   std::cout << typeid(employee).name() << std::endl; // Employee (statically known at compile-time)   std::cout << typeid(ptr).name() << std::endl;      // Person * (statically known at compile-time)   std::cout << typeid(*ptr).name() << std::endl;     // Employee (looked up dynamically at run-time                                                      //           because it is the dereference of a                                                      //           pointer to a polymorphic class)}

结果如下:

PersonEmployeePerson*Employee
0 0