什么是RTTI﹖
在C++ 环境中﹐头文件(header file) 含有类之定义(class definition)亦即包含有关类的结构资料(representational information)。但是﹐这些资料只供编译器(compiler)使用﹐编译完毕后并未留下来﹐所以在执行时期(at run-time) ﹐无法得知对象的类资料﹐包括类名称、数据成员名称与类型、函数名称与类型等等。例如﹐两个类Figure和Circle﹐其之间为继承关系。
若有如下指令﹕
Figure *p;
p = new Circle();
Figure &q = *p;
在执行时﹐p指向一个对象﹐但欲得知此对象之类资料﹐就有困难了。同样欲得知q 所参考(reference) 对象的类资料﹐也无法得到。RTTI(Run-Time Type Identification)就是要解决这困难﹐也就是在执行时﹐您想知道指针所指到或参考到的对象类型时﹐该对象有能力来告诉您。随着应用场合之不同﹐所需支持的RTTI范围也不同。最单纯的RTTI包括﹕
●类识别(class identification)──包括类名称或ID。
●继承关系(inheritance relationship)──支持执行时期的「往下变换类型」(downward casting)﹐亦即动态变换类型(dynamic casting) 。
在对象数据库存取上﹐还需要下述RTTI﹕
●对象结构(object layout) ──包括属性的类型、名称及其位置(position或offset)。
●成员函数表(table of functions)──包括函数的类型、名称、及其参数类型等。
其目的是协助对象的I/O 和持久化(persistence) ﹐也提供调试讯息等。
若依照Bjarne Stroustrup 之建议〔注1 〕﹐C++ 还应包括更完整的RTTI﹕
●能得知类所实例化的各对象 。
●能参考到函数的源代码。
●能取得类的有关在线说明(on-line documentation) 。
其实这些都是C++ 编译完成时所丢弃的资料﹐如今只是希望寻找个途径来将之保留到执行期间。然而﹐要提供完整的RTTI﹐将会大幅提高C++ 的复杂度﹗
通过RTTI,能够通过基类的指针或引用来检索其所指对象的实际类型。c++通过下面两个操作符提供RTTI。
(1)typeid:返回指针或引用所指对象的实际类型。
(2)dynamic_cast:将基类类型的指针或引用安全的转换为派生类型的指针或引用。
对于带虚函数的类,在运行时执行RTTI操作符,返回动态类型信息;对于其他类型,在编译时执行RTTI,返回静态类型信息。
dynamic_cast 操作符
如果dynamic_cast转换指针类型失败,则返回0;如果转换引用类型失败,则抛出一个bad_cast类型的异常。
可以对值为0的指针使用dynamic_cast,结果为0。
dynamic_cast会首先验证转换是否有效,只有转换有效,操作符才进行实际的转换。
if (Derived *derivedPtr = dynamic_cast<Derived *>(basePtr))
{
// use the Derived object to which derivedPtr points
}
else
{ // basePtr points at a Base object
// use the Base object to which basePtr points
}
也可以使用dynamic_cast将基类引用转换为派生类引用:dynamic_cast<Type&>(val)
因为不存在空引用,所以不能像指针一样对转换结果进行判断。不过转换引用类型失败时,会抛出std::bad_cast异常。
try
{
const Derived &d = dynamic_cast<const Derived&>(b);
}
catch (bad_cast) {
// handle the fact that the cast failed.
}
注意:dynamic_cast在将父类cast到子类时。父类必需要有虚函数。比如在以下的代码中将CBasic类中的test函数不定义成
virtual时,编译器会报错:error C2683: dynamic_cast : “CBasic”不是多态类型
typeid操作符
typeid能够获取一个表达式的类型:typeid(e)。
如果操作数不是类类型或者是没有虚函数的类,则获取其静态类型;如果操作数是定义了虚函数的类类型,则计算运行时类型。
typeid最常见的用途是比较两个表达式的类型,或者将表达式的类型与特定类型相比较。typeid运算符返回一个对type_info对象的引用。
可以:
typeid(*pg).name();
Base *bp;
Derived *dp;
// compare type at run time of two objects
if (typeid(*bp) == typeid(*dp))
{
// bp and dp point to objects of the same type
}
// test whether run time type is a specific type
if (typeid(*bp) == typeid(Derived))
{
// bp actually points a Derived
}
注意:如果是typeid(bp),则是对指针进行测试,这会返回指针(bp)的静态编译时类型(Base *)。
如果指针p的值是0,,并且指针所指的类型是带虚函数的类型,则typeid(*p)抛出一个bad_typeid异常。
type_info类
type_info类的实现因编译器的不同而不同。但如下几个常用的操作符和函数是c++标准要求必须实现的:“t1 == t2”、“t1 != t2”、“t.name()”。
typeid操作符的返回类型就是type_info,正因为type_info提供了“==”操作符,才可以进行上面提到的“if (typeid(*bp) == typeid(*dp))”判断。
type_info的默认构造函数、拷贝构造函数、赋值操作符都定义为private,创建type_info对象的唯一方法就是使用typeid操作符。
name()函数返回类型名字的c-style字符串,但字符串的格式可能不同的编译器略有不同。下面是在vc2008编译器下的测试。
请注意*pb和pb *pd和pd
// expre_typeid_Operator.cpp
// compile with: /GR /EHsc
#include <iostream>
#include <typeinfo.h>
class Base
{
public:
virtual void vvfunc() {}
};
class Derived : public Base {};
using namespace std;
int main()
{
Derived* pd = new Derived;
Base* pb = pd;
int i = 0;
cout << typeid( i ).name() << endl; // prints "int"
cout << typeid( 3.14 ).name() << endl; // prints "double"
cout << typeid( pb ).name() << endl; // prints "class Base *"
cout << typeid( *pb ).name() << endl; // prints "class Derived"
cout << typeid( pd ).name() << endl; // prints "class Derived *"
cout << typeid( *pd ).name() << endl; // prints "class Derived"
delete pd;
}
类型转换运算符
C++中提供4中类型转运算符,分别是:static_cast、dynamic_cast、reinterpret_cast和const_cast;
1、static_cast可以完全替代c的类型转换,而且在对对象指针之间的类型转换时,可以将父类指针转换成子类指针,也可以将子类指针转换成父类指针,但是如果两个类不相关则无法相互转换。 需注意的是,如果父类指针指向一个父类对象,此时将父类指针转换成子类指针虽然可以通过static_cast实现,但是这种转换很不安全;如果父类指针本身就指向子类指针则不存在安全问题。编译时类型检查。
class Base(){};
class Derived:public Base{};
Base *b1 = new Base;
Base *b2 = new Derived;
Derived *b2d1 = static_cast<Derived*>(b1); //转换成功不安全
Derived *b2d2 = static_cast<Derived*>(b2); //转换成功安全
int i = 0;
double d = 1.9;
int d2i = static_cast<int>d;
double i2d = static_cast<double>i;
2.dynamic_cast 只能用于对象指针之间的类型转换,可以将父类指针转换成子类指针,也可以将子类指针转换成父类指针,转换结果也可以是引用,但是dynamic_cast不等同于static_cast。dynamic_cast在将父类指针转换为子类指针的过程中,需要对其背后的对象类型进行检查,以保证类型完全匹配,而static_cast不会。只有当一个父类指针指向一个子类对象,且父类中包含虚函数时,使用dynamic_cast将父类指针转换成子类指针才会成功,否则返回空指针,如果是引用则抛出异常。
3.const_cast转换过程中增加或删除const属性。
class Test{};
const Test *t1 = new Test;
Test *t2 = const_cast<Test*>(t1); //转换成功
const_cast不是万能的。他可以修改指向一个值的指针,但是修改const值的结果是不确定的。如下:
#include<iostream>
using std::cout;
using std::endl;
void change(const int *pt, int n);
int main()
{
int pop1 = 38383;
const int pop2 = 2000;
cout << "pop1,pop2: " << pop1 << ", " << pop2 << endl;
change(&pop1, -103);
change(&pop2, -103);
cout << "pop1, pop2: " << pop1 << ", " << pop2 << endl;
return 0;
}
void change(const int * pt, int n)
{
int *pc;
pc = const_cast<int *>(pt);
*pc += n;
}
由于pop2本身被生命为const,所以不能修改pop2。