C++运行时类型识别

时间:2021-12-10 03:07:14

通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。

通过下面两个操作符提供 RTTI:

1. typeid 操作符,返回指针或引用所指对象的实际类型。

2. dynamic_cast 操作符,将基类类型的指针或引用安全地转换为派生类型的指针或引用。

注意,这些操作符只为带有一个或多个虚函数的类返回动态类型信息,对于其他类型,返回静态(即编译时)类型的信息。对于带虚函数的类,在运行时执行 RTTI 操作符,但对于其他类型,在编译时计算 RTTI 操作符。

 

1:dynamic_cast 操作符

可以使用 dynamic_cast 操作符将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或指针。与 dynamic_cast 一起使用的指针必须是有效的——它必须为 0 或者指向一个对象。

dynamic_cast 涉及运行时类型检查。如果绑定到引用或指针的对象不是目标类型的对象,则 dynamic_cast 失败。如果转换到指针类型的 dynamic_cast 失败,则 dynamic_cast 的结果是 0 值;如果转换到引用类型的 dynamic_cast 失败,则抛出一个 bad_cast 类型的异常。

可以对值为 0 的指针应用 dynamic_cast,这样做的结果是 0。

 

假定 Base 是至少带一个虚函数的类,并且 Derived 类派生于Base 类。如果有一个名为 basePtr 的指向 Base 的指针,就可以像这样在运行时将它强制转换为指向 Derived 的指针:

if (Derived *derivedPtr = dynamic_cast<Derived*>(basePtr))
{
// use the Derived object to which derivedPtr points
} else { // BasePtr points at a Base object
// use the Base object to which basePtr points
}

 

 

也可以使用 dynamic_cast 将基类引用转换为派生类引用,这种 dynamic_cast 操作的形式如下: dynamic_cast< Type& >(val)

只有当 val 实际引用一个 Type 类型对象,或者 val 是一个 Type 派生类型的对象的时候,dynamic_cast 操作才将操作数 val 转换为想要的 Type& 类型。当转换失败的时候,它抛出一个 std::bad_cast 异常,该异常在库头文件 typeinfo 中定义。

重写前面的例子如下:

void f(const Base &b)
{
try {
const Derived &d = dynamic_cast<const Derived&>(b);
// use the Derived object to which b referred
} catch (bad_cast) {
// handle the fact that the cast failed
}
}

 

 

2:typeid 操作符

typeid 操作符使程序能够返回一个表达式的类型。typeid 表达式形如:typeid(e) 。这里 e 是任意表达式或者是类型名。

typeid 操作符的结果是名为 type_info 的标准库类型的对象引用。

如果表达式的类型是类类型且该类包含一个或多个虚函数,则表达式的动态类型可能不同于它的静态编译时类型。例如,如果表达式对基类指针解引用,则该表达式的静态编译时类型是基类类型;但是,如果指针实际指向派生类对象,则 typeid 操作符将说表达式的类型是派生类型。

typeid 操作符可以与任何类型的表达式一起使用。内置类型的表达式以及常量都可以用作 typeid 操作符的操作数。如果操作数不是类类型或者是没有虚函数的类,则 typeid 操作符指出操作数的静态类型;如果操作数是定义了至少一个虚函数的类类型,则在运行时计算类型。

 

typeid 最常见的用途是比较两个表达式的类型,或者将表达式的类型与特定类型相比较:

Base *bp;
Derived *dp;
if (typeid(*bp) == typeid(*dp)) {
// bp and dp point to objects of the same type
}
if (typeid(*bp) == typeid(Derived)) {
// bp actually points to a Derived
}

 

注意,typeid 的操作数是表示对象的表达式——测试 *bp,而不是 bp。

 

如果指针 p 的值是 0,那么,如果 p 的类型是带虚函数的类型,则typeid(*p) 抛出一个 bad_typeid 异常;如果 p 的类型没有定义任何虚函数,则结果与 p 的值是不相关的。正像计算表达式 sizeof一样,编译器不计算 *p,它使用 p 的静态类型,这并不要求 p 本身是有效指针。

 

3:type_info 类

type_info 类的确切定义随编译器而变化,但是,标准保证所有的实现将至少提供下表 列出的操作。

t1 == t2

 如果两个对象 t1 和 t2 类型相同,就返回 true;否则,返回false

t1 != t2

 如果两个对象 t1 和 t2 类型不同,就返回 true;否则,返回false

t.name()

返回 C 风格字符串,这是类型名字的可显示版本。类型名字用系统相关的方法产生

t1.before(t2)

返回指出 t1 是否出现在 t2 之前的 bool 值。before 强制的次序与编译器有关

默认构造函数和复制构造函数以及赋值操作符都定义为 private,所以不能定义或复制 type_info 类型的对象。程序中创建 type_info 对象的唯一方法是使用 typeid 操作符。

 

name 函数为 type_info 对象所表示的类型的名字返回 C 风格字符串。给定类型所用的值取决于编译器:

int iobj;
cout << typeid(iobj).name() << endl
<< typeid(8.16).name() << endl
<< typeid(std::string).name() << endl
<< typeid(Base).name() << endl
<< typeid(Derived).name() << endl;

 

name 返回的格式和值随编译器而变化。在我们的机器上执行时,这个程序产生下面的输出:

i
d
Ss
4Base
7Derived