运行时类型识别

Posted xiaojianliu

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了运行时类型识别相关的知识,希望对你有一定的参考价值。

运行时类型识别(run-time type identification,RTTI)的功能由两个运算符实现:

  • typeid 运算符,用于返回表达式的类型。
  • dynamic_cast 运算符,用于将基类的指针或引用安全地转换成派生类的指针或引用。

将上面的运算符用于某种类型的指针或引用,并且该类型含有虚函数时,运算符将使用指针或引用绑定对象的动态类型。

这两个运算符特别适用于以下情况:使用基类对象的指针或引用执行某个派生类的操作并且该操作不是虚函数。一般来说,只要有可能应该尽量使用虚函数。当操作被定义成虚函数时,编译器将根据对象的动态类型选择正确的函数版本。

但是不是所有时候都能定义一个虚函数,假设无法使用虚函数,则可以使用一个 RTTI 运算符。RTTI 运算符有很多的潜在风险:程序员必须清楚地知道转换的目标类型并且必须检查类型转换是否被成功执行。

使用 RTTI 必须要加倍小心。在可能的情况下,最好定义虚函数而非直接接管类型管理的重任。

dynamic_cast 运算符

dynamic_cast 运算符的使用形式如下形式:

dynamic_cast<type*>(e)
dynamic_cast<type&>(e)
dynamic_cast<type&&>(e)
  • type 必须是一个类类型,并且通常情况下该类型应该含有虚函数。

  • 第一种形式中,e 必须是一个指针。

  • 第二种形式中,e 必须是一个左值。

  • 第三种形式中,e 不能是左值。

  • 在上面的所有形式中,e 的类型必须符合以下三个条件中的任意一个:

    • e 的类型是目标 type 的公有派生类。
    • e 的类型是目标 type 的公有基类。
    • e 的类型就是目标 type 的类型。

    如果符合,则类型转换可以成功,否则,转换失败。

  • 如果一条 dynamic_cast 语句的转换目标是指针类型并且失败了,则结果为0。如果转换目标是引用类型并且失败了,则 dynamic_cast 运算符将抛出一个 bad_cast 异常。

指针类型的 dynamic_cast

假定 Base 类至少含有一个虚函数, Derived 是 Base 的公有派生类。如果有一个指向 Base 的指针 bp,则可以在运行时将它转换成指向 Derived 的指针:

if (Derived *dp = dynamic_cast<Derived*>(bp))
{
    //@ 使用 dp 指向的 Derived 对象
}
else //@ bp 指向一个 Base 对象
{
    //@ 使用 bp 指向的 Base 对象
}

可以对一个空指针执行 dynamic_cast,结果是所需类型的空指针。

在条件部分执行 dynamic_cast 操作可以确保类型转换和结果检查在同一条表达式中完成。

引用类型的 dynamic_cast

因为不存在所谓的空引用,所以对于引用类型来说无法使用与指针类型完全相同的错误报告策略,当对引用的类型转换失败时,程序将抛出一个名为 std::bad_cast 的异常,该异常定义在 typeinfo 标准库头文件中。

void f(const Base &b)
{
    try {
        const Derived &d = dynamic_cast<const Derived&>(b);
        //@ 使用 b 引用的 Derived 对象
    }
    catch (bad_cast){
        //@ 处理类型转换失败的情况
    }
}

typeid 运算符

typeid 运算符允许向表达式提问:你的对象是什么类型?

typeid 表达式的形式是:

typeid(e)
  • e 可以是任意表达式或类型的名字。
  • typeid 操作的结果是一个常量对象的引用,该对象的类型是标准库类型 type_info 或者 type_info 的公有派生类型。
  • type_info 类定义在 typeinfo 头文件。

typeid 运算符可以用于任意类型的表达式:

  • 顶层 const 将被忽略。
  • 如果表达式是一个引用类型,则 typeid 返回该引用所引对象。
  • 如果 typeid 作用于数组,所得的结果是数组类型而非指针类型。
  • 如果 typeid 作用于函数,所得的结果是函数类型而非函数指针类型。

当运算对象不属于类类型或者是一个不包含任何虚函数的类时,typeid 运算符指示的是运算对象的静态类型、

当运算对象是定义了至少一个虚函数的类的左值时,typeid 的结果知道运行时才会求得。

使用 typeid 运算符

通常情况下使用 typeid 比较两条表达式的类型是否相同或者比较一条表达式的类型是否与指定的类型相同:

Derived *dp = new Derived;
Base *bp = dp;  //@ 两个指针都指向 Derived 对象

//@ 在运行时比较两个对象的类型
if (typeid(*bp) == typeid(*dp)) {
    //@ bp 和 dp 指向同一类型的对象
}

//@ 检查运行时类型是否是指定的类型
if (typeid(*bp) == typeid(Derived)) {
    //@ bp 实际指向 Derived 对象
}

typeid 作用于指针时,返回的结果是该指针的静态编译时类型:

//@ bp 是指向 Base 的指针,下面的判断将永远失败
if (typeid(bp) == typeid(Derived))
{
    //@ 此处代码永远不会执行
}

只有当类型含有虚函数时,编译器才会对表达式求值。

如果类型不含有虚函数,则 typeid 返回表示的静态类型,编译器无须对表达式求值也能知道表达式的静态类型。

如果表达式的动态类型可能与静态类型不同,则必须在运行时对表达式求值以确定返回的类型。

对于 typeid(*p),如果 p 所指的类型不含有虚函数,则 p 不必非的是一个有效指针。否则,*p 将在运行时求值,此时p必须是一个有效的指针。如果 p 是空指针,则 typeid(*p) 将抛出 bad_typeid 异常。

使用 RTTI

在具有继承关系的类实现相等运算符:

class Base 
{
    friend bool operator == (const Base&, const Base&);
public:
    //@ Base 的接口成员

protected:
    virtual bool equal(const Base&) const;
};

class Derived : public Base
{
public:
    //@ Derived 的接口成员

protected:
    bool equal(const Base&) const;
};

bool operator == (const Base& lhs, const Base& rhs)
{
    //@ 如果 typeid 不相同,返回false;否则虚调用 equal
    //@ 因为 equal 是虚函数,所以会动态调用 equal 的版本
    return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}

虚 equal 函数

继承体系中的每个类必须定义自己的 equal 函数。派生类的所有函数要做的第一件事都是相同的,那就是将实参的类型转换成派生类的类型:

bool Derived::equal(const Base& rhs) const
{
    //@ 因为已经清楚两个类型是相等的,所以转换过程不会抛出异常
    auto r = dynamic_cast<const Derived&>(rhs);
    //@ 执行比较两个 Derived 对象的操作并返回结果
}

基类的 equal 函数无须实现对形参进行类型转换:

bool Base::equal(const Base& rhs) const
{
    //@ 执行比较 Base 对象的操作
}

type_info 类

type_info 类定义在 typeinfo 头文件中,并且至少提供:

操作 说明
t1 == t2 如果 type_info 对象t1 和 t2 表示同一种类型,返回 true,否则返回 false
t1 != t2 如果 type_info 对象t1 和 t2 表示不同的类型,返回 true,否则返回 false
t.name() 返回 C 风格字符串,表示类型名字的可打印形式。类型的名字生成方式因系统而异。
t1.before(t2) 返回一个 bool 值,表示t1 是否位于 t2 之前。before 所采用的顺序关系是依赖于编译器的。
  • 因为 type_info 类通常作为基类出现,所以一般会定义虚析构函数。
  • type_info 类没有默认构造函数,而且它的拷贝和移动构造函数以及赋值运算符都被定义成删除的。因此,无法定义或者拷贝type_info 类型的对象,也不能为 type_info 类型的对象赋值。创建 type_info 对象的唯一途径是使用 typeid 运算符。
int arr[10];
Derived d;
Base* p = &d;

cout << typeid(42).name() << endl;
cout << typeid(arr).name() << endl;
cout << typeid(std::string).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(p).name() << endl;

输出:

int
int [10]
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> >
class Derived
class Base *

以上是关于运行时类型识别的主要内容,如果未能解决你的问题,请参考以下文章

在 Python 多处理进程中运行较慢的 OpenCV 代码片段

如何识别哪个活动调用片段

linux打开终端如何启动scala,如何在终端下运行Scala代码片段?

运行时类型识别

Android 逆向Android 逆向通用工具开发 ( Android 平台运行的 cmd 程序类型 | Android 平台运行的 cmd 程序编译选项 | 编译 cmd 可执行程序 )(代码片段

运行时类型识别