跳转到内容

C++ 编程/RTTI

来自维基教科书,自由的教科书

运行时类型信息 (RTTI)

[编辑 | 编辑源代码]

RTTI 指的是系统能够报告对象的动态类型并在运行时(而不是在编译时)提供有关该类型的信息的能力,并且当一致地使用时,它可以成为一个强大的工具,可以简化程序员在管理资源方面的工作。

考虑一下你已经了解的关于dynamic_cast关键字的内容,假设我们有以下类层次结构

class Interface
{
public:
        virtual void GenericOp() = 0;// pure virtual function
};

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

假设我们还有一个类型为Interface*的指针,如下所示

Interface* ptr_interface;

假设出现一种情况,我们被迫假定但没有保证该指针指向SpecificClass类型的对象,并且我们想要调用该类的成员SpecificOp()。要动态转换为派生类型,我们可以使用dynamic_cast,如下所示

SpecificClass* ptr_specific = dynamic_cast<SpecificClass*>(ptr_interface);
if( ptr_specific ){
    // our suspicions are confirmed -- it really was a SpecificClass
    ptr_specific->SpecificOp();
}else{
    // our suspicions were incorrect -- it is definitely not a SpecificClass.
    // The ptr_interface points to an instance of some other child class of the base InterfaceClass.
    ptr_interface->GenericOp();
};

使用dynamic_cast,程序将基类指针转换为派生类指针,并允许调用派生类成员。但是要非常小心:如果你试图转换的指针不是正确类型,那么dynamic_cast将返回一个空指针。

我们也可以将dynamic_cast与引用一起使用。

SpecificClass& ref_specific = dynamic_cast<SpecificClass&>(ref_interface);

它的工作原理与指针几乎相同。但是,如果被转换对象的实际类型不正确,那么dynamic_cast不会返回空(不存在空引用)。相反,它会抛出一个std::bad_cast异常。

语法
    typeid( object );

typeid运算符用于在运行时确定对象的类。它返回对std::type_info对象的引用,该对象描述“对象”,并且一直存在到程序结束。如果“对象”是解除引用的空指针,那么操作将抛出一个std::bad_typeid异常。

std::bad_typeid类的对象派生自std::exception,由typeid和其他对象抛出。

注意
C++98 标准要求在编译单元中使用运算符typeid之前包含头文件<typeinfo>。否则,程序将被认为是非法的。

在只需要类信息的情况下,通常建议使用typeid而不是dynamic_cast<class_type>,因为typeid在类型或非解引用值上应用是一个常数时间过程,而dynamic_cast必须在运行时遍历其参数的类派生格。但是,你不应该依赖于确切的内容,例如由std::type_info::name()返回的内容,因为这对于编译来说是特定于实现的。

通常,仅对指向多态类类型对象的指针或引用的解引用(即typeid(*ptr)typeid(ref))使用typeid才有用(一个类至少有一个虚成员函数)。这是因为这些是与运行时类型信息相关的唯一表达式。任何其他表达式的类型在编译时都是静态已知的。

示例
#include <iostream>
#include <typeinfo>  //for 'typeid' to work

class Person {
public:
   // ... Person members ...
   virtual ~Person() {}
};

class Employee : public Person {
   // ... Employee members ...
};

int main () {
   Person person;
   Employee employee;
   Person *ptr = &employee;
   // The string returned by typeid::name is implementation-defined
   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)
}

输出(确切的输出因系统而异)

 Person
 Employee
 Person*
 Employee


在RTTI中,它在这种设置中使用

const std::type_info& info = typeid(object_expression);

有时我们需要知道对象的精确类型。typeid运算符返回对标准类std::type_info的引用,其中包含有关类型的信息。该类提供了一些有用的成员,包括==!=运算符。最有趣的方法可能是

const char* std::type_info::name() const;

这个成员函数返回一个指向 C 样式字符串的指针,其中包含对象类型的名称。例如,使用我们之前示例中的类

const std::type_info &info = typeid(*ptr_interface);
std::cout << info.name() << std::endl;

这个程序会打印类似[1] SpecificClass的内容,因为这是指针ptr_interface的动态类型。

typeid实际上是一个运算符而不是一个函数,因为它也可以对类型进行操作

const std::type_info& info = typeid(type);

例如(有点循环地)

const std::type_info& info = typeid(std::type_info);

将给出描述type_info对象的type_info对象。后一种用法不是 RTTI,而是 CTTI(编译时类型识别)。

局限性

[编辑 | 编辑源代码]

RTTI 有一些局限性。首先,RTTI 只能与多态类型一起使用。这意味着你的类必须至少有一个虚函数,无论是直接还是通过继承。其次,由于存储类型所需的额外信息,一些编译器需要特殊的开关来启用 RTTI。

请注意,对指针的引用不会在 RTTI 下工作

void example( int*& refptrTest )
{
        std::cout << "What type is *&refptrTest : " << typeid( refptrTest ).name() << std::endl;
}

将报告int*,因为typeid()不支持引用类型。

RTTI 的误用

[编辑 | 编辑源代码]

RTTI 应该在 C++ 程序中谨慎使用。有很多原因。最重要的是,其他语言机制,如多态和模板,几乎总是优于 RTTI。与所有事物一样,存在例外,但关于 RTTI 的通常规则或多或少与goto语句相同。不要将其用作正确、更健壮设计的捷径。只有在你有一个很好的理由这样做,并且只有在你确切知道自己在做什么时,才使用 RTTI。


  1. (由 std::type_info::name() 返回的确切字符串依赖于编译器)。
华夏公益教科书