天天看点

C++的RTTI机制的理解,typeid和dynamic_cast<>

RTTI全名叫run time type identification,在运行时进行类型检查/辨认,他能完成一些虚函数解决不了的问题

其应用场景举例:

假设父类A定义了一些虚函数作为蓝图,众多子类B、C、D、E·····都继承了A,父类中的虚函数不可能囊括子类的所有行为,这些子类总会有一些独有的派生行为,如果我们想要用父类指针指向众多子类的众多对象,打算用这个父类指针遍历这些对象,那么我们用这个父类指针只能调到众多子类对象中共有的成员/方法,因为我们我们的遍历过程使用的是父类A指针,当我们想调用B、C、D、E等的独有派生成员/方法,怎么办呢?当然是把A指针分别转为B、C、D、E···的指针,那么问题来了,我们怎么知道要把A指针转为B指针、还是C指针、还是D指针··呢?B、C、D、E··的对象传入时是用的A指针,这没问题,但是传进来以后再转回原先的类型就难了。

RTTI就用于解决这一问题,靠的是两个运算符:typeid和dynamic_cast<>。

有了这两个运算符,上述问题就解决了:

(1)使用typeid来解决,举例

B b1,b2,b3;
C c1,c2;
D d1,d2;
A *pA[20];    
pA[0]=&b1; pA[1]=&b2; pA[2]=&b3; 
pA[3]=&c1; pA[4]=&c2; 
pA[5]=&d1; pA[6]=&d2; 
for(int i=0;i<7;i++)
{
    if(  typeid(*pA[i])  ==   typeid(B)  )
    {
        B *pb = static_cast<B*>(pA[i]);
        do_some_B(pb);//做一些B类独有的行为
    }
    if(  typeid(*pA[i])  ==   typeid(C)  )
    {
        C *pC = static_cast<C*>(pA[i]);
        do_some_C(pC);//做一些C类独有的行为
    }
	````//其他代码	
}
           

typeid的使用方法类似于sizeof,参数既可以是类型,也可以是实例。

由上述例子看出typeid虽然在一定程度上解决了上述问题,但它仍有不足,继续以上问题:假设又有一个类BB,继承了类B,BB的对象显然是拥有B的public方法的,如果在上述代码基础上增加一部分:

B b1,b2,b3;
C c1,c2;
D d1,d2;
BB bb1,bb2;
A *pA[20];    
pA[0]=&b1; pA[1]=&b2; pA[2]=&b3; 
pA[3]=&c1; pA[4]=&c2; 
pA[5]=&d1; pA[6]=&d2; 
pA[7]=&bb1; pA[8]=&bb2; 

for(int i=0;i<9;i++)
{
    if(  typeid(*pA[i])  ==   typeid(B)  )
    {
        B *pb = static_cast<B*>(pA[i]);
        do_some_B(pb);//做一些B类独有的行为
        //BB的对象bb1、bb2的typeid是BB,不是B,故无法进入该if,但我们期望他们能进入该if,因为他们明明拥有B类的独有行为
    }
    if(  typeid(*pA[i])  ==   typeid(C)  )
    {
        C *pC = static_cast<C*>(pA[i]);
        do_some_C(pC);//做一些C类独有的行为
    }
    if(  typeid(*pA[i])  ==   typeid(BB)  )
    {//bb1、bb2会进入这个if,
        C *pBB = static_cast<BB*>(pA[i]);
        do_some_BB(pBB);//做一些BB类独有的行为,但是因为BB有B的方法,do_some_BB的功能与上面第一条if的功能会存在重复
    }
	````//其他代码	
}
           

这里注释的比较清楚了,在上述应用情境中,使用typeid仍然不爽,对于这种情景,可以使用dynamic_cast<>运算符来解决,这个运算符在上一篇博文中已经记录过了,这里再次复习一下

(2)使用dynamic_cast<>运算符来解决,举例

把上面代码的第一条if语句改为:

if(  NULL != dynamic_cast<B*>(pA[i])  )
    {//B和BB的对象b1、b2、bb1、bb2都可以进入该if进行处理
        B *pb = static_cast<B*>pA[i];
        do_some_B(pb);//做一些B类独有的行为,更近一步做一些BB类独有的行为
    }
           

或者改为:

try
{
    if( B &rB = dynamic_cast<B&>(*pA[i])  )
    {//B和BB的对象b1、b2、bb1、bb2都可以进入该if进行处理
        do_some_B(rB);//做一些B类独有的行为       
    }
}
catch(std::bad_cast&)//转换失败的异常
{
    //弹出提示,或者跳过
}
           

这两种方法都可以,不同的是,指针转换失败会返回NULL,而引用转换失败会抛出异常,相比来说,还是检查指针失败较为简洁,个人更倾向于第一种。