运行时类型识别

时间:2023-02-02 03:07:28

前言

我们知道,在类的继承中可以通过基类的指针(或引用)指向派生类的对象,利用多态性执行派生类中提供的功能。但这仅限与调用基类中声明的虚函数。如果希望对于一部分派生类的对象,调用派生类中引入的新函数,则无法通过基类指针进行。我们可以通过static_cast,将基类指针转换成派生类指针。但这样做并不安全,只能在指针所指向的对象类型明确的情况下执行。而有时只有在运行时才能知道指针所指对象的实际类型是声明。C++中提供了两种运行时类型识别的机制,下面分别介绍。

1、dynamic_cast

dynamic_cast是与static_cast,const_cast,reinterpret_cast并列的4中类型转换操作符之一。它可以将基类的指针显示转换成派生类的指针(引用也一样),并在转换时会检查指针所指向的对象的实际类型是否与转换的目的类型兼容,如果兼容转换才会发生,否则:

  • 如果执行的是指针类型的转换,会得到空指针
  • 如果执行的是引用类型的转换,会抛出异常。
    另外,转换前类型必须是指向多态类型的指针,或多态类型的引用,这是因为C++只为多态类型在运行时保存用于运行时类型识别的信息。这也从另一个方面说明为什么非多态类型不宜被公有继承。

示例

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void fun1() {
        cout << "Base::fun1()" << endl;
    }
};

class Base1 : public Base
{
public:
    void fun1() {
        cout << "Base1::fun1()" << endl;
    }
    virtual void fun2() {
        cout << "Base1::fun2()" << endl;
    }
};

class Base2 : public Base1
{
public:
    void fun1() {
        cout << "Base2::fun1()" << endl;
    }
    void fun2() {
        cout << "Base2::fun2()" << endl;
    }
};

void fun(Base *b)
{
    b->fun1();
    Base1 *d = dynamic_cast<Base1 *> (b);
    if (d)
        d->fun2();
}

int main(int argc, char **argv) {

    Base b;
    fun(&b);
    Base1 d1;
    fun(&d1);
    Base2 d2;
    fun(&d2);

    return 0;
}

/* *Base::fun1() *Base1::fun1() *Base1::fun2() *Base2::fun1() *Base2::fun2() */

由于fun1函数是基类中定义的函数,通过Base类的指针b可以直接调用fun1函数,fun2函数是派生类Base1中引入的新函数,只能对Base1和Base2类的对象调用。因此在fun函数中,将Base指针b转换成Base1类指针d。并根据转换结果是否成功,来决定是否调用fun2函数。

2、typeid

typeid是C++中的一个关键字,用它可以获得一个类型的相关信息。语法形式如下:

typeid (表达式)
//or
typeid (类型说明符)

typeid既可以作用于一个表达式,也可以作用于一个类型的说明符。例如:

typeid(5+3)     //将typeid作用于一个表达式
typeid(int)     //将typeid作用于一个类型说明符

1通过typeid得到一个type_info类型的常引用。type_info是C++标准库中的一个类,用于在运行时表示类型信息,它的定义在typeinfo头文件中。type_info类有一个名为name的函数,用来获取类型的名称。原型如下:

const char* name() const

如果typeid所作用于的表达式具有多态类型,那么这个表达式会被求值,用typeid得到的是用于描述表达式求值结果的运行时类型(动态类型)的type_info对象的常引用。而如果表达式具有非多态类型,那么用typeid得到的表达式静态类型,由于之歌静态类型在编译时就能确定,这是表达式不会被求值。
示例

#include <iostream>
#include <typeinfo>
using namespace std;

class Base
{
public:
    virtual void fun1() {
        cout << "Base::fun1()" << endl;
    }
};

class Base1 : public Base
{
public:
    void fun1() {
        cout << "Base1::fun1()" << endl;
    }
    virtual void fun2() {
        cout << "Base1::fun2()" << endl;
    }
};

void fun(Base *b)
{
    const type_info &info1 = typeid(b);
    const type_info &info2 = typeid(*b);

    cout << "typeid(b): " << info1.name() << endl;
    cout << "typeid(*b): " << info2.name() << endl;
    if (info2 == typeid(Base)) {
        cout << "A Base class" << endl;
    }
}

int main(int argc, char **argv) {

    Base b;
    fun(&b);
    Base1 d1;
    fun(&d1);

    return 0;
}

输出结果为:

typeid(b): P4Base
typeid(*b): 4Base
A Base class
typeid(b): P4Base
typeid(*b): 5Base1

在函数fun中,由于b的类型是Base ,而Base是多态类型,所以用typeid(*b)得到的是b指针所指向的对象的具体类型,因此两次调用fun函数所得到的结果不同。虽然b是指向多态类型的指针,但其自身确实Base 一个指针类型,而不是多态类型,所以两次调用结果相同。

注意
C++标准中并没有明确规定type_info对象的name()成员函数所返回字符串的构造方式,因此不同编译器可能的实现会有所不同。运行时类型识别机制虽然灵活,但是却要付出一定的效率代价,因此不同把它视为常规方法。多数情况下,派生类的特殊性是可以通过在基类中定义虚函数加以体现的,运行时类型检查只是一种辅助性手段,在必要时才使用。