C++ 06_C++运行时类型识别(RTTI)机制

一、C++运行时类型识别(RTTI)机制

一般情况下,C++程序在编译期间就能确定一个表达式的类型,但是当存在多态时,有些表达式的类型在编译期间就无法确定了,必须等到程序运行后根据实际的环境来确定。 下面的例子演示了这种情况:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

//基类
class Base
{
public:
    virtual void func();
protected:
    int m_a;
    int m_b;
};
void Base::func(){ cout<<"Base"<<endl; }

//派生类
class Derived: public Base
{
public:
    void func();
private:
    int m_c;
};
void Derived::func(){ cout<<"Derived"<<endl; }

int main()
{
    Base *p;
    int n;
  
    cin>>n;
    if(n == 1)
    {
        p = new Base();
    }
    else
    {
        p = new Derived();
    }
    cout<<typeid(*p).name()<<endl;

    return 0;
}

输入 1,运行结果为: Base 输入其它值,运行结果为: Derived

基类 Base 中包含了一个虚函数,派生类 Derived 又定义了一个原型相同的函数遮蔽了它,这就构成了多态。p 是基类的指针,可以指向基类对象,也可以指向派生类对象;*p 表示指针 p 指向的对象。

从代码中可以看出,用户输入的数字不同,*p 表示的对象就不同,typeid 获取到的类型也就不同,编译器在编译期间无法预估用户的输入,所以无法确定 *p 的类型,必须等到程序真的运行了、用户输入完毕了才能确定 *p 的类型。

根据前面讲过的知识,C++ 的对象内存模型主要包含了以下几个方面的内容: 1、如果没有虚函数也没有虚继承,那么对象内存模型中只有成员变量。 2、如果类包含了虚函数,那么会额外添加一个虚函数表,并在对象内存中插入一个指针,指向这个虚函数表。 3、如果类包含了虚继承,那么会额外添加一个虚基类表,并在对象内存中插入一个指针,指向这个虚基类表。

如果类包含了虚函数,那么该类的对象内存中还会额外增加类型信息,也即 type_info 对象。以上面的代码为例,Base 和 Derived 的对象内存模型如下图所示: 编译器会在虚函数表 vftable 的开头插入一个指针,指向当前类对应的 type_info 对象。当程序在运行阶段获取类型信息时,可以通过对象指针 p 找到虚函数表指针 vfptr,再通过 vfptr 找到 type_info 对象的指针,进而取得类型信息。下面的代码演示了这种转换过程:

1
**(p->vfptr - 1)

程序运行后,不管 p 指向 Base 类对象还是指向 Derived 类对象,只要执行这条语句就可以取得 对应对象的type_info。

编译器在编译阶段无法确定 p 指向哪个对象,也就无法获取 *p 的类型信息,但是编译器可以在编译阶段做好各种准备,这样程序在运行后可以借助这些准备好的数据来获取类型信息。这些准备包括: 1、创建 type_info 对象,并在 vftable 的开头插入一个指针,指向 type_info 对象; 2、将获取类型信息的操作转换成类似 **(p->vfptr - 1) 这样的语句。

Tips: 这样做虽然会占用更多的内存,效率也降低了,但这是没办法的事情,编译器实在是无能为力了。

这种在程序运行后确定对象的类型信息的机制称为 运行时类型识别(Run-Time Type Identification,RTTI)

在 C++ 中,只有类中包含了虚函数时才会启用 RTTI 机制,其他所有情况都可以在编译阶段确定类型信息。

下面是 RTTI 机制的一个具体应用,可以让代码根据不同的类型进行不同的操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
using namespace std;

//基类
class People
{
public:
    virtual void func(){ }
};

//派生类
class Student: public People{ };

int main()
{
    People *p;
    int n;
  
    cin>>n;
    if(n <= 100)
    {
        p = new People();
    }
    else
    {
        p = new Student();
    }

    //根据不同的类型进行不同的操作
    if(typeid(*p) == typeid(People))
    {
        cout<<"I am human."<<endl;
    }
    else
    {
        cout<<"I am a student."<<endl;
    }

    return 0;
}

多态(Polymorphism) 是面向对象编程的一个重要特征,它极大地增加了程序的灵活性,C++、C#、Java 等“正统的”面向对象编程语言都支持多态。但是支持多态的代价也是很大的,有些信息在编译阶段无法确定下来,必须提前做好充足的准备,让程序运行后再执行一段代码获取,这会消耗更多的内存和 CPU 资源。