天天看点

C++中的RTTI

      C++中的RTTI是指“运行时刻类型识别”。RTTI:Run-Time Type Identification。那么C++中的RTTI有什么作用呢?RTTI允许“用指向基类的指针或引用来操纵对象”的程序能够获取到“这些指针或引用所指对象”的实际派生类类型。在C++中,为了支持RTTI提供了两个操作符:

1、dynamic_cast操作符,它允许在运行时刻进行类型转换。把基类指针转换成派生类指针,或把指向基类的左值(一般指引用)转换成派生类的引用。

          用法:dynamic_cast < type-id > ( expression_r_r )

        该运算符把expression_r_r转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression_r_r也必须是一个指针,如果type-id是一个引用,那么expression_r_r也必须是一个引用。

2、typeid操作符,它指出指针或引用指向的对象的实际派生类型。

       但是,对于要获取派生类类型的信息,dynamic_cast和typeid操作符的操作数的类型必须是带有一个或多个虚拟函数的类类型。即,对于带有虚拟函数的类而言,RTTI操作符是运行时刻的事件,而对于其他类而言,它只是编译时刻的事件。我们应该尽量少用RTTI,而尽可能的多使用C++的静态类型系统(即编译时刻类型检查),因为它是更加安全有效的。

下面具体介绍RTTI的两个操作符

一、dynamic_cast操作符

        dynamic_cast操作符用来把一个类类型对象的指针转换成同一类层次结构中的其他类的指针,同时也可以用它把一个类类型对象的左值转换成同一类层次结构中的其他类的引用。如果针对指针类型的dynamic_cast失败,则dynamic_cast的结果是0。如果针对引用类型的dynamic_cast失败,则dynamic_cast会抛出一个异常。

class employee
{
public:
	virtual int salary() ;
};

class manager : public employee
{
public:
	int salary() ;
};

class programmer : public employee
{
public:
	int salary() ;
	int bonus() ;
};

void company::payroll(employee *pe)
{
	programmer *pm = dynamic_cast<programmer*> (pe) ;

	//如果pe指向programmer类型的一个对象
	//则dynamic_cast成功
	//并且pm指向programmer对象的开始
	if (pm){
		//用pm调用programmer::bonus()
	}

	//如果pe不是指向programmer类型的一个对象
	//则dynamic_cast失败
	//并且pm的值为0
	else{
		//使用employee的成员函数
	}
}
           

       上面的这一小段程序体现了dynamic_cast的用法。dynamic_cast把它的操作数pe转换成programmer*型。如果指向programmer类型的对象,则该强制转换成功。否则,转换失败,dynamic_cast的结果为0。

      所以,dynamic_cast操作符一次执行两个操作。它检验所请求的转换是否真的有效,只有在有效时,它才会执行转换,而检验过程发生在运行时刻。

       在上面的这个例子中,如果运行时刻pe实际上指向一个programmer对象,则dynamic_cast成功,并且pm被初始化,指向一个programmer对象。否则,dynamic_cast操作的结果为0,而且pm被初始化为0。在使用结果指针pm之前,我们必须通过测试dynamic_cast操作符的结果来检验转换是否成功。company::payroll()函数的一个较好的定义如下:

void company::payroll(employee *pe)
{
	//dynamic_cast和测试在同一条件表达式中
	if (programmer *pm = dynamic_cast<programmer*>(pe))
	{
		//用pm调用programmer::bonus()
	}
	else
	{
		//使用employee的成员函数
	}
}
           

     上面的例子是dynamic_cast把一个基类指针转换成派生类指针。dynamic_cast也可以把一个基类类型的左值转换成派生类类型的引用。因为不存在空引用,所以不可能通过比较dynamic_cast的结果是否为0来检验dynamic_cast是否成功。如果上例想使用引用而不是指针,则条件:

if (programmer *pm = dynamic_cast<programmer*>(pe))
           

就不能被改写为:

if (programmer &pm = dynamic_cast<programmer&>(pe))
           

       当dynamic_cast被用来转换引用类型时,它会以一种不同的方式报告错误。如果一个引用的dynamic_cast失败,则会抛出一个异常。那么,为了使用引用类型的dynamic_cast,上个例子必须被重写:

void company::payroll(employee &pe)
{
	try
	{
		programmer &pm = dynamic_cast<programmer&>(pe) //注意pe是引用
			//用pm调用programmer::bonus()
	}
	catch(std::bad_cast)
	{
		//使用employee的成员函数
	}
}
           

二、typeid操作符

       typeid操作符,它在程序中可用于获取一个表达式的类型。如果表达式是一个类类型,并且含有一个或多个虚拟成员函数,则答案会不同于表达式本身的类型。例如,如果表达式是一个基类的引用,则typeid会指出底层对象的派生类类型。例如:

programmer pobj ;
employee &re = pobj ;
//name()是programmer的成员函数
cout << typeid(re).name() << endl ;
           

      typeid操作符的操作数re的类型是employee。但是,因为re是带有虚拟函数的类类型的引用,所以typeid操作符的结果指出,底层对象的类型是programmer类型。(而不是操作数的类型employee)。使用typeid操作符时,程序文本文件必须包含C++标准库定义的头文件<typeinfo>。

       typeid操作符可用来做什么呢?当程序通过基类指针或引用操纵一个对象时,程序需要找到被操纵的对象的实际类型,以便正确的列出对象的属性。为了找到对象的实际类型,我们可以使用typeid。

      typeid操作符必须与表达式或类型名一起使用。例如,内置类型的表达式或常量可以被用typeid的操作数。当操作数不是类类型时,typeid操作符会指出操作数的类型:

int iobj ;
cout << typeid(iobj).name() << endl ;  //打印出int,iobj的内置类型是int
cout << typeid(8.16).name() << endl ;  //打印出double
           

       当typeid操作符的操作数是类类型,但不是带有虚拟函数的类类型时,typeid操作符会指出操作数的类型,而不是底层对象的类型。这一点需要特别注意。例如:

class Base { /*没有虚拟函数*/};
class Derived : public Base { /*没有虚拟函数*/};

Derived dobj ;
Base *pb = &dobj ;

cout << typeid(*pb).name() << endl ;  //打印出的结果是:Base
           

      typeid操作符的操作数是Base类型的,即表达式*pb的类型。因为Base不是一个带有虚拟函数的类类型,所以typeid的结果指出,表达式的类型是Base,尽管指向的底层对象的类型是Derived。

继续阅读