运行时类型识别也是面试时常问的问题,今天整理一下,作为学习和分享~
在存在虚函数表的继承关系时,每个虚函数表前面都设置有一个type_info指针,用于支持RTTI,RTTI是为多态而生成的信息,包括对象继承关系,对象本身的描述等,只有具有虚函数的对象才会生成。
运行时类型识别(run-time type identification,RTTI)的功能由两个运算符实现:
- typeid运算符,用于返回表达式的类型;
- dynamic_cast运算符,用于将基类的指针或引用安全的转换为派生类的指针;
这两个运算符的适用场景为使用基类对象的指针或引用执行某个派生类操作且该操作对象不是虚函数。
一般来讲,应该尽量使用虚函数,当操作被定义为虚函数时,编译器将根据对象的动态类型自动选择正确的函数版本。假设无法使用虚函数,则使用RTTI运算符。
dynamic_cast运算符
其使用形式如下:
dynamic_cast<type*>(e) //e必须为一个有效指针
dynamic_cast<type&>(e) //e必须是一个左值
dynamic_cast<type&&>(e) //e不能是左值
其中,type必须为一个类类型,通常情况下该类型中含有虚函数。
e类型转换成功需满足以下三个条件中的任意一个:
- e的类型是目标type的公有派生类;
- e的类型是目标type的公有基类;
- e的类型是目标type的类型;
若是转换失败,dynamic_cast语句的转换目标为指针类型时结果为0(也叫返回空指针),引用类型时抛出bad_cast异常且异常定义在typeinfo标准库头文件中。
代码如下
#include <iostream>
using namespace std;
class A{
public:
void test1(){
cout<<"A::test1"<<endl;};
virtual void test2(){
cout<<"A::test2"<<endl;};
};
class B:public A{
public:
void test1(){
cout<<"B::test1"<<endl;};
virtual void test2(){
cout<<"B::test2"<<endl;};
};
void f(const A &a){
try{
const B &b = dynamic_cast<const B&>(a);
}
catch(bad_cast){
cout<<"cast failed"<<endl;
return;
}
cout << "cast successful"<<endl;
}
int main(){
/*为指针类型时的向下转换*/
A* p1 = new B;
if(B* p2 = dynamic_cast<B*>(p1)){
//确保指针p2在if语句外不可访问
cout<<"cast successful"<<endl; //转换成功时打印
}
else{
cout<<"cast failed"<<endl;
}
/*为引用类型时的向下转换*/
A &a = *p1;
f(a);
return 0;
}
运行结果
cast successful
cast successful
dynamic_cast在向下转换时提供RTTI机制使得其转换相比于static_cast更为安全。
typeid运算符
为RTTI提供的第二个运算符是typeid运算符,它允许程序向表达式请求其对象类型。typeid表达式的形式是typeid(e),其中e可以是任意表达式或类型的名字。typeid操作的结果是一个常量对象的引用,该对象的类型是标准库类型type_info或者type_info的公有派生类。
代码如下
#include <iostream>
using namespace std;
class A{
public:
void test1(){
cout<<"A::test1"<<endl;};
virtual void test2(){
cout<<"A::test2"<<endl;};
};
class B:public A{
public:
void test1(){
cout<<"B::test1"<<endl;};
virtual void test2(){
cout<<"B::test2"<<endl;};
};
int main(){
B* p1 = new B;
A* p2 = p1;
if (typeid(*p1) == typeid(*p2)) {
cout<<"same"<<endl;
}
if (typeid(*p2) == typeid(B)) {
cout<<"same"<<endl;
}
cout<< typeid(*p2).name()<<endl;
return 0;
}
运行结果
same
same
1B //编译器不同,此条运行结果可能稍有差异,这里博主用的xcode作为IDE
在第这两个if语句中,比较了p1,p2所指向的对象动态类型是否相同。值得注意的是,typeid应该作用于对象,若将*p1改为p1,则代码永远不会被执行。
总结
-
RTTI由dynamic_cast和typeid两个运算符实现,typeid允许程序向表达式请求其对象类型;
-
dynamic_cast使用RTTI可以使得向下转换更为安全。dynamic_cast对指针转换失败时返回空指针,引用则抛出异常;
-
尽量使用虚函数而非RTTI。