天天看点

RTTI(typeid、dynamic_cast)

  • 什么是RTTI?

RTTI是 “run-time type identification”的缩写,意思是运行时类型识别,它提供了运行时确定对象类型的方法。

RTTI的功能由两个运算符实现:

1)dynamic_cast运算符,用于将基类的指针或引用安全转换成派生类的指针或引用。

2)typeid运算符,用于返回表达式类型。

我们想使用基类对象的指针或引用执行某个派生类操作,并且该操作不是虚函数。我们知道定义成虚函数,执行时会发生动态绑定,这也是我们希望的,但是并非任何时候都能定义成一个虚函数的。假设我们无法使用虚函数,则可以使用一个RTTI运算符,来达到相同的效果。另外,使用RTTI运算符蕴含着更多潜在的危险。程序员必须清楚地知道转换的目标类型并且必须检查类型转换是否被成功执行。

例:

class A
{
public:
     void fun1(){ cout << "i am A->fun1()" << endl; }
};
class B:public A
{

     void fun1(){ cout << "i am B->fun1()" << endl; }
};
int main()
{
     A* pa = new B;
     pa->fun1();
     return ;
}
           

事实上我们希望编辑器能自动识别到所指向对象类型,从而调用相对应的操作–其实这里就是达到动态绑定的效果。

我们知道只要把基类A中的fun1变成虚函数就可以实现。我们可以使用RTTI运算符来实现。

当然用之前我们先来分别学习这两个运算符

  • dynameic_cast

    dynamic_cast主要用于在多态的时候,它允许在运行时刻进行类型转换,从而使程序能够在一个类层次结构中安全地转换类型,把基类指针(引用)转换为派生类指针(引用)。

    首先我们来看代码:

A* pa = new B;
     B* pb = dynamic_cast<B*>(pa);
     if (pb == NULL)
     {
         cout << "error" << endl;
     }
     cout << typeid(*pb).name() << endl;
     return ;
           

当我们使用dynamic_cast类型转化后,类型就对了。在这里面dynamic_cast做了什么呢?

当类中存在虚函数时,编译器就会在类的成员变量中添加一个指向虚函数表的vptr指针,每一个class所关联的type_info object也经由virtual table被指出来,通常这个type_info object放在表格的第一个位置。当我们进行dynamic_cast时,编译器会帮我们进行语法检查。如果指针的静态类型和目标类型相同,那么就什么事情都不做;否则,首先对指针进行调整,使得它指向vftable,并将其和调整之后的指针、调整的偏移量、静态类型以及目标类型传递给内部函数。其中最后一个参数指明转换的是指针还是引用。两者唯一的区别是,如果转换失败,前者返回NULL,后者抛出bad_cast异常。

class A
{
public:
     virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
     void Print(){cout << "i am B" << endl;}
};
class C :public A
{
public:
     void Print(){ cout << "i am C" << endl; }
};
void Handle(A* a)
{
     if (dynamic_cast<B*>(a))
     {
         cout << "This is B" << endl;
     }
     else if (dynamic_cast<C*>(a))
     {
         cout << "This is C" << endl;
     }
     else
     {
         cout << "error" << endl;
     }
}
int main()
{
     A* pa = new B;
     Handle(pa);
     return ;
}

           
  • typeid

    typeid表达式的形式是typeid(e),e可以为任意表达式或名字,内置类型或自定义类型都可以。

    struct A

    {

};

int main()

{

int a;

char c;

A _a;

cout << typeid(a).name() << endl;

cout << typeid(c).name() << endl;

cout << typeid(_a).name() << endl;

return 0;

}

输出 int char struct A,说明它支持内置类型,同时也支持自定义类型。

我们发现当我们调用typeid后通常会调用name()函数,这说明typeid返回的是一个结构体或者类,然后通过类或者结构体来调用name()成员。其实typeid返回的是一个type_info类型的对象。type_info类定义在typeinfo头文件中。既然typeid返回的是一个type_info,我们就要对type_info有所了解

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(__type_info_node* __ptype_info_node = &__type_info_root_node) const;
    const char* raw_name() const;
private:
    void *_M_data;
    char _M_d_name[];
    type_info(const type_info& _Rhs);
    type_info& operator=(const type_info& _Rhs);
    static const char * _Name_base(const type_info * __type_info_node* __ptype_info_node);
    static void _Type_info_dtor(type_info *);
};
           

去掉那些多余的宏,这是VS2013中type_info的定义。

type_info中没有默认构造函数,同时可以看到它的拷贝构造和赋值运算符都是私有的,那么我们就无法定义或拷贝type_info类型额度对象,也不能为type_info类型对象赋值。

type_info类的name成员函数返回一个C风格的字符串,表示对象类型名字。

操作:

t1==t2

t1和t2表示同一类型,返回true;否则返回false

t1!=t2

t1和t3表示不同类型,返回true;否则返回false

t.name()

返回一个C风格字符串,表示类型名字。

t1.before(t2)

返回一个bool值,表示t1是否在t2之前。

typeid

如果操作数不是类类型或者是没有虚函数的类,则获取其静态类型;如果操作数是定义了虚函数的类类型,则计算运行时类型。

typeid的使用:

1)使用type_info中的name()成员返回类型名称

这里有一点需要注意,我们来看代码

class A
{
public:
     void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
     void Print(){cout << "i am B" << endl;}
};
int main()
{
     A* pa = new B;
     cout << typeid(pa).name() << endl;
     cout << typeid(*pa).name() << endl;
     return ;
}
           

输出 class A * class A

我们发现调用两次typeid,参数不同,返回的类型也不同,当我们传入pa时,pa是一个A*类型的指针,所以typeid计算出pa的类型为class A*,第二次传入*pa,传入的是一个对象,所以输出的是class A。

同时我们又看到我们让一个A类型的指针,指向一个B,可是得到的还是A类型,也就是它的指针所指对象类型。注意这里我们没有定义成虚函数。

那我们把Print定义成虚函数会发生什么呢?

class A
{
public:
     virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
     void Print(){cout << "i am B" << endl;}
};
int main()
{
     A* pa = new B;
     cout << typeid(*pa).name() << endl;
     return ;
}
           

输出 class B

这是为什么呢?这就是因为RTTI,当我们没有将Print变成虚函数时,typeid是在编译期间,检测得到的是它的静态类型,就像刚开始输出的class A一样,当类中存在虚函数时,typeid是在运行期间,得到的是它的动态类型,所以输出class B。

2)使用type_info的==和!=

class A
{
public:
     virtual void Print(){cout << "i am A" << endl;}
};
class B :public A
{
public:
     void Print(){cout << "i am B" << endl;}
};
class C :public A
{
public:
     void Print(){ cout << "i am C" << endl; }
};
void Handle(A* a)
{
     if (typeid(*a) == typeid(A))
     {
         cout << "This is A" << endl;
     }
     else if (typeid(*a) == typeid(B))
     {
         cout << "This is B" << endl;
     }
     else if (typeid(*a) == typeid(C))
     {
         cout << "This is C" << endl;
     }
     else
     {
         cout << "error" << endl;
     }
}
int main()
{
     A* pa = new B;
     Handle(pa);
     //cout << typeid(*pa).name() << endl;
     delete pa;
     pa = new C;
     Handle(pa);
     return ;
}
           

输出 This is B This is C

  • 使用RTTI

    在某些情况下RTTI非常有用,比如当我们想为具有继承关系的类实现相运算符时。

    一种容易想到的方法是定义一套虚函数,令其在继承体系的各个层次分别执行相等判断。但是虚函数的基类版本和派生类版本必须具有相同的形参类型。

    如果想实现“==”操作符。假设类层次中只有2个类型,那么需要4个函数:

    bool operator==(const Base&, const Base&)

    bool operator==(const Derived&, const Derived &)

    bool operator==(const Derived &, const Base&)

    bool operator==(const Base&, const Derived &)

    如果类层次中有4个类型,就要实现16个操作符函数,这种实现就太麻烦了。

    其实我们可以使用RTTI解决这个问题。

    我们定义的相等运算符的形参设置为基类的引用,然后在用typeid或dynamic_cast检查类型是否一致,一致的话返回true,否则返回false,然后如果类型一致则调用equal函数。

    只定义1个“==”操作符函数,每个类定义一个虚函数equal。

#include<iostream>
#include<typeinfo>
using namespace std;
class Base
{
    friend bool operator==(const Base&, const Base&);
public:
    // interface members for Base
protected:
    virtual bool equal(const Base&) const;
    // data and other implementation members of Base
};

bool Base::equal(const Base &rhs) const
{
    // do whatever is required to compare to Base objects
    return true;
}

class Derived : public Base
{
    friend bool operator==(const Base&, const Base&);
public:
    // other interface members for Derived
private:
    bool equal(const Base&) const;
    // data and other implementation members of Derived
};

bool Derived::equal(const Base &rhs) const
{
    if (const Derived *dp = dynamic_cast<const Derived *>(&rhs))
    {
// do work to compare two Derived objects and return result
        return true;
    }
    else
        return false;
}

bool operator==(const Base &lhs, const Base &rhs)
{
    //如果类型一致调用equal
    return typeid(lhs) == typeid(rhs) && lhs.equal(rhs);
}

int main()
{
    Base* pa = new Derived;
    Derived d;
    if (d==*pa)
    {
        cout << "RTTI" << endl;
    }
    return ;
}