C++ 编程/RTTI
RTTI 指的是系统能够报告对象的动态类型并在运行时(而不是在编译时)提供有关该类型的信息的能力,并且当一致地使用时,它可以成为一个强大的工具,可以简化程序员在管理资源方面的工作。
dynamic_cast
[编辑 | 编辑源代码]考虑一下你已经了解的关于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
和其他对象抛出。
在只需要类信息的情况下,通常建议使用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 应该在 C++ 程序中谨慎使用。有很多原因。最重要的是,其他语言机制,如多态和模板,几乎总是优于 RTTI。与所有事物一样,存在例外,但关于 RTTI 的通常规则或多或少与goto
语句相同。不要将其用作正确、更健壮设计的捷径。只有在你有一个很好的理由这样做,并且只有在你确切知道自己在做什么时,才使用 RTTI。
- ↑ (由 std::type_info::name() 返回的确切字符串依赖于编译器)。