运行期类型识别RTTI
继承的赋值兼容规则告诉我们,基类引用/指针可以指向派生类对象,那么,给定一个基类引用/指针,能否获得该引用/指针实际所指对象的数据类型呢?解决这一问题的语法机制就是运行期时类型识别(Run-time Type Identification,RTTI),是指在程序中能够使用基类引用或者指针来检索该引用或指针所指对象的实际派生类型。
C++语言通过typeid和dynamic_cast两个操作符支持运行期类型识别。其中,
(1)typeid:返回引用或指针所指对象的实际数据类型;
(2)dynamic_cast:将基类引用/指针安全地转换为派生类引用/指针。
操作符typeid
操作符typeid,获得对象或者类型名的类型信息,结果存放在一个type_info类对象中。注意,使用 type_info 类需要包含标准库头文件 typeinfo。例如,
class Vehicle{ … … };
class Bus: virtual public Vehicle { … … };
class Ship: virtual public Vehicle { … … };
class Amphibus: public Bus, public Ship { …… };
int main() {
Busbus(10); Vehicle * pvc = &bus;
consttype_info &ti = typeid(*pvc); //运行期类型识别
cout<<ti.name()<<endl;
inta;
cout<<typeid(a).name()<<endl; //编译期类型识别
return0;
}
这里,由于typeid的操作数*pvc是一个类对象且类中包含虚函数,所以,在运行期识别类型信息,得出其实际数据类型是Bus;如果操作数不是类类型或者是不包含虚函数的类,则在编译期即可确定操作数的类型信息,即编译期类型识别。
操作符dynamic_cast
操作符dynamic_cast,称为动态转换,用于进行基类和派生类之间进行指针或引用的类型转换。对于从派生类到基类的上行转换(upcasting),dynamic_cast与static_cast是等效的;但是,对于从基类到派生类的下行转换(downcasting),dynamic_cast会进行运行期的类型检查,此时dynamic_cast比static_cast更为安全。例如,
int main() {
Busbus(10); Vehicle * pvc = &bus;
Bus*pbus = dynamic_cast<Bus*>(pvc); //转换成功
pbus->print();
Vehicle &rvc = bus;
Ship &rship = dynamic_cast<Ship&>(rvc);//转换失败
rship.print();
return0;
}
这里,指针pvc实际指向的就是一个Bus类对象bus,将pvc指针转换为Bus类型的指针是合理的,因此,第一次dynamic_cast会成功地实现类型转换。
而引用rvc实际引用的是一个Bus类对象,我们却要将其转换为Ship类引用,这就是指鹿为马,因此,第二次dynamic_cast会转换失败。如果不对转换失败做进一步的处理,那么,这种失败会导致程序执行出错并且退出,错误提示为:
abnormal program termination
动辄就死的程序,性情太过刚烈,没有人敢用。所谓好死不如赖活着,健壮的程序不应该遇到一点问题就崩溃,因此,我们应该对转换失败的dynamic_cast做一些特殊处理。实际上,转换失败的dynamic_cast会给出一些失败的线索,
(1)dynamic_cast为指针,则转换失败的指针值为0;
(2)dynamic_cast为引用,则转换失败会抛出异常bad_cast。
显然,我们可以检查转换结果指针是否为0来避免程序执行崩溃,例如,
int main() {
Busbus(10); Vehicle * pvc = &bus;
//方法1:通过检查结果指针是否为0来避免程序崩溃
if ( Bus *pbus = dynamic_cast<Bus*>(pvc)) {
pbus->print();
} else {
cout<<”动态类型转换失败”<<endl;
}
return0;
}
此外,转换为引用的失败会抛出bad_cast异常,我们还可以通过捕获bad_cast异常来避免程序执行崩溃。
int main() {
//方法2:通过捕获处理bad_cast异常来避免程序崩溃
Vehicle &rvc = bus;
try {
Ship& pship = dynamic_cast<Ship&>(rvc);
pship->print();
} catch (bad_cast) {
cout<<”动态类型转换失败”<<endl;
}
return0;
}
通过返回值检查和异常捕获,我们可以处理程序执行时出现的诸多突发问题,编写出更加健壮可靠的程序。

