一、RTTI概述
RTTI的功能由两个运算符实现,一个是typeid,用来返回表达式的类型;另一个是dynamic_cast,作用是将基类的指针或引用安全地转为子类的指针或引用
二、typeid
typeid的表达式形式是typeid(e),e可以是任意表达式或者类型,typeid的结果是个const对象的引用,该对象的类型是标准库type_info或type_info的子类,type_info类中有个name成员函数,返回值是个表示类型名字的字符串,所以typeid的结果可通过name方法查看类型名字
当一个对象中没有虚函数时,typeid得到的时运算对象的静态类型,如果有虚函数,那么,typeid得到的时运算对象的动态类型
示例
class base
{
public:
base(){}
virtual ~base(){}
};
class derive:public base
{
public:
derive(){}
~derive(){}
};
int main(int argc, char const *argv[])
{
derive *dp=new derive();
base *bp=dp;
if (typeid(*dp)==typeid(*bp)) {
cout<<"typeid(*dp)==typeid(*bp)"<<endl;
cout<<typeid(*dp).name()<<","<<typeid(*bp).name()<<endl;
}
else {
cout<<"typeid(*dp)!=typeid(*bp)"<<endl;
cout<<typeid(*dp).name()<<","<<typeid(*bp).name()<<endl;
}
return 0;
}
如果把基类中的virtual关键字去掉,输出结果如下
因为没有虚函数,所以typeid解析的是bp的静态类型
typeid一般作用于对象,所以typeid中的表示是指针的解引用而不是指针。如果typeid作用于指针,无论是否有虚函数,typeid的结果都是指针的静态类型
将上述代码中的main函数修改
int main(int argc, char const *argv[])
{
derive *dp=new derive();
base *bp=dp;
if (typeid(dp)==typeid(bp)) {
cout<<"typeid(dp)==typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(bp).name()<<endl;
}
else {
cout<<"typeid(dp)!=typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(bp).name()<<endl;
}
return 0;
}
输出结果如下
上述代码中,if分支永远不会执行,因为typeid作用于指针,结果都是静态类型
如果typeid作用于指针,那么,当执行typeid(p)时,指针p可以无效;
int main(int argc, char const *argv[])
{
derive *dp=new derive();
base *bp=nullptr;//空指针
if (typeid(dp)==typeid(bp)) {
cout<<"typeid(dp)==typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(bp).name()<<endl;
}
else {
cout<<"typeid(dp)!=typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(bp).name()<<endl;
}
return 0;
}
但是如果执行typeid(*p),那么指针必须有效,否则会抛出一个bad_typeid异常
int main(int argc, char const *argv[])
{
derive *dp=new derive();
base *bp=nullptr;
if (typeid(dp)==typeid(*bp)) {
cout<<"typeid(dp)==typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(*bp).name()<<endl;
}
else {
cout<<"typeid(dp)!=typeid(bp)"<<endl;
cout<<typeid(dp).name()<<","<<typeid(*bp).name()<<endl;
}
return 0;
}
在运行时执行typeid(*bp),对bp进行解引用,而bp是个空指针,所以抛出异常
typeid一般用于比较两个对象的类型是否相等
三、dynamic_cast
dynamic_cast中尖括号中只能是指针或者引用,用在继承体系中类对象的转换(所以不能用于内置类型的转化)。因为dynamic_cast执行的强转发生在运行时,所以可能耗费较大的运行成本,所以,在对效率要求较高的程序中不要频繁使用dynamic_cast。dynamic_cast 转换成功的话,返回的是指向类的指针或引用,否则返回 nullptr。
因为dynamic_cast中尖括号中只能是指针或者引用,所以,dynamic_cast有两种形式:1、dynamic_cast<type*>(e)。2.dynamic_cast<type&>(e)。
dynamic_cast用于向上转换(子类指针或引用转父类指针或引用)时,dynamic_cast和static_cast是一样的。
dynamic_cast用于向下转换(父类指针或引用转子类指针或引用)时,dynamic_cast会进行运行时类型检查,因此,dynamic_cast比static_cast更安全。 向下转换是否成功与指针或引用指向的实际对象有关,要转换的指针指向对象的实际类型与转换以后的对象类型要相同,否则转换失败。
当基类的指针或者引用指向子类对象时,使用dynamic_cast时,基类中一定要有虚函数,否则无法编译通过。这是因为运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表中,只有定义了虚函数的类才有虚函数表。
dynamic_cast中的e和type的关系通常有三种情况:1、e的类型是type的子类。2、e的类型是type的基类。3、e的类型就是type。
示例1:e的类型就是type、e的类型是type的基类
class base
{
public:
base(){}
~base(){}
void func(){cout<<__func__<<"in base"<<endl;}
};
class derive:public base
{
public:
derive(){}
~derive(){}
void func(){cout<<__func__<<"in derive"<<endl;}
};
int main(int argc, char const *argv[])
{
base *pb=new base();
derive *pd=new derive();
base *pb2=dynamic_cast<base*>(pb);
base *pb3=dynamic_cast<derive*>(pd);
pb2->func();
pb3->func();
}
这种从子类向基类的转换或者是平转的情况,用不用dynamic_cast的效果都一样
示例2:e的类型是type的子类,基类的指针指向子类对象
int main(int argc, char const *argv[])
{
base *pb=new derive();
derive *pd=dynamic_cast<derive*>(pb);
pd->func();
}
上述代码中,因为基类base不是多态类(没有虚函数),因此无法找到运行是信息进行运行时类型检查,所以,此时即使基类指针指向子类对象,也无法进行dynamic_cast,代码无法通过编译,
将base的析构函数设置为virtual后,代码可以通过编译,运行后,调用子类中的func
示例3:e的类型是type的子类,基类的指针指向基类对象
int main(int argc, char const *argv[])
{
base *pb=new base();
if (derive *pd=dynamic_cast<derive*>(pb)) {//缩小pd的作用域,即使转换失败,if-else分支之外的代码也无法接触到该指针,比较安全
pd->func();
}
else {
cout<<pd<<endl;
}
}
此时编译可以通过,但是转换失败,dynamic_cast返回一个空指针,因为要转换的指针指向对象的实际类型与转换以后的对象类型不相同,所以转换失败。所以当基类指针指向基类对象时,使用dynamic_cast向子类转换会失败
使用dynamic_cast作用于引用时的规则和指针一样,只不过要使用try-catch捕获异常
int main(int argc, char const *argv[])
{
base t;
base &rb=t;
try {
derive &rd=dynamic_cast<derive&>(rb);
}
catch(bad_cast) {
cout<<"bad_cast"<<endl;
}
}
参考
《C++ Primer》
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出