第13章 运行时类型信息
第13章 运行时类型信息
《C++大学教程(第3版)》是2005年清华大学出版社出版的图书,作者是那格勒,译者是侯普秀。ISBN 9787302098492
13.1 概述
运行时类型信息(run time type information,RTTI)是一种机制,采用该机制可以在执行时确定对象类型而不是在编译时确定对象类型。它包括两个部分:动态转型态(dynamic casting)和关键字 typeid。
13.2 动态转型态
有一个基类的指针或引用,可以指向或引用一个派生类对象。在基类中函数声明为virtual,就可以实现多态。但若基类没有声明函数fun1,派生类有fun1,那么基类指针或引用就不能调用函数fun1。
例如:
class Base
{
//...
}
class Derived : public Base
{
public:
fun1();
}
void test(Base* ptr)
{
ptr->fun1(); // Compiler error; no Base::fun1
}
可以用 Derived* 作为形参。
但是如果由于某种原因必须使用 Base* ,该怎么办?即如何知道一个指针是指向一个基类实例还是派生类实例呢?这就要用到 dynamic_cast 向下强制转换。
13.3 动态转型态是如何工作的
dynamic_cast 是C++强制转换的新风格的一种。
如果在向下强制转换中使用基类指针,并且该指针确实指向的是一个派生类对象,那么派生类指针将是非零的。如果基类指针没有指向一个派生类对象,那么派生类指针将是零。
在基类引用的情况下,如果确实引用了一个派生类对象,就会成功地创建一个派生类引用。然而,当引用没有引用派生类对象时,则不可能创建一个“空”引用,因此会抛出一个 std::bad_cast 类型的异常。
注意:因为动态转型态机制使用了编译器的虚函数表,所以基类至少必须包含一个虚函数。
例子:
class Base { public: Base(); virtual ~Base(); }; class Derived : public Base { public: Derived(); ~Derived(); }; void func(Base* ptrBase, Base& refBase) { Derived* ptrDerived = dynamic_cast<Derived*>(ptrBase); if (ptrDerived) cout << "Pointing to a Derived.\n"; else cout << "Pointing to a Base.\n"; try { Derived& refDerived = dynamic_cast<Derived&>(refBase); cout << "Referring to a Derived.\n"; } catch (const std::bad_cast& e) { cout << "Referring to a Base." << e.what() << endl; } } int main() { Base* ptrBase1 = new Base(); Base& refBase1 = *ptrBase1; func(ptrBase1, refBase1); delete ptrBase1; Base* ptrBase2 = new Derived; Base& refBase2 = *ptrBase2; func(ptrBase2, refBase2); delete ptrBase2; return 0; } /* Pointing to a Base. Referring to a Base.Bad dynamic_cast! Pointing to a Derived. Referring to a Derived. 请按任意键继续. . . */
13.4 关键字 typeid
关键字 typeid 用于获取有关对象、类型、变量、任何有效的C++表达式的类型信息。与执行安全的向下强制转换的关键字 dynamic_cast 不同,关键字 typeid 用于非多态的类型中(如基本类型、不带虚函数的类)。
typeid 的使用要求包含头文件 typeinfo。为了获取名为arg(此处arg是一个类型、对象名或任何表达式)的相关类型信息,可以编写以下代码:
#include <typeinfo>
//...
typeid(arg)
type_info 类
关键字 typeid 的使用创建了类 std::type_info 的临时实例,该类在头文件 typeinfo 中被定义。
类 type_info 的定义如下:
namespace std { class type_info { public: virtual ~type_info(); bool operator==(const type_info& _Rhs) const; bool operator!=(const type_info& _Rhs) const; bool before(const type_info& _Rhs) const; const char* name() const; const char* raw_name() const; private: void *_M_data; char _M_d_name[1]; type_info(const type_info& _Rhs); type_info& operator=(const type_info& _Rhs); }; }
1. 两个关系运算符确定两种类型是否相同。
2. 如果*this 在编译器的归并序列(Collating Sequence)中的显示参数之前出现,则成员函数before()返回true。该函数很少被使用。
3. 成员函数 name() 返回用于表示类型名的编译器特有的字符串。
例子:
#include <iostream> #include <typeinfo> template <typename T, typename U> void typeTest(const T&, const U&) { const std::type_info& first = typeid(T); const std::type_info& second = typeid(U); bool test = (first == second); std::cout << "Type " << first.name() << " is " << (test ? "equal" : "unequal") << " to type " << second.name() << '\n'; if (!test) { std::cout << "Type " << first.name() << " comes " << ((first.before(second)) ? "before " : "after ") << "type " << second.name() << '\n'; } } int main() { char a = 'A'; int b = 0, c = 0; long d = 0L; typeTest(b, c); typeTest(b, d); typeTest(a, b); /* Type int is equal to type int Type int is unequal to type long Type int comes before type long Type char is unequal to type int Type char comes before type int 请按任意键继续. . . */ return 0; }
例子2:(基类和派生类)
template <typename T> void typeTest(const T& arg) { std::cout << typeid(arg).name() << '\n'; } void test(Base* ptr) { typeTest(ptr); typeTest(*ptr); } int main() { // Base 至少有个虚函数 Base* ptr = new Base; test(ptr); delete ptr; ptr = new Derived; test(ptr); delete ptr; /* class Base * class Base class Base * class Derived 请按任意键继续. . . */ return 0; }
常记溪亭日暮,沉醉不知归路。兴尽晚回舟,误入藕花深处。争渡,争渡,惊起一滩鸥鹭。
昨夜雨疏风骤,浓睡不消残酒。试问卷帘人,却道海棠依旧。知否?知否?应是绿肥红瘦。