c++ RTTI 运行时类型识别

文章摘自 C++从入门到精通(第四版)清华大学出版社

目录

一.什么是RTTI

二.RTTI与引用

三.RTTI与多重继承

四.RTTI映射语法

dynamic_cast:用于安全的向下映射

const_cast:用于映射常量和变量

static_cast:为了行为良好和行为较好使用的映射,如向上转型和类型自动转换。

reinterpret_cast:将某一类型映射回原有类型时使用 

运行时类型识别(Run-time Type Identification,RTTI)是在只有一个指向基类的指针或引用时所确定的一个对象的类型。

在编写程序的过程中,往往只提供了一个对象的指针,但通常在使用时需要明确这个指针的确切类型。利用RTTI就可以方便的获取某个对象指针的确切类型并进行控制。

一.什么是RTTI

RTTI可以在程序运行时通过某一对象的指针确定该对象的类型。许多程序设计人员都使用过虚基类编写面向对象的功能。通常在基类中定义了所有子类的通用属性或行为。但有些时候子类会存在属于自己的一些公有的属性或行为,这时通过基类对象的指针如何调用子类特有的属性或行为呢?首先需要确定的是这个基类对象属于哪个子类,然后将该对象转换成子类对象再进行调用。

下图展示了具有特有功能的类。

由上图中可以看出CBint类和CBstring类都继承于CBase,这三个类存在于一个公共方GetName(),CBint类有自己的方法GetInt,CBString类有自己的方法GetString().如果想通CBase类指针调用CBint类或CBString类的特有方法就必须确定指针的具体类。下面代码完成了这样的功能。

//RTTI
#include<bits/stdc++.h>
using namespace std;
class CBase
{
	public:
		virtual char *GetName()=0;
};
class CBint:public CBase
{
	public:
		char *GetName()
		{
			return "CBint";
		}
	    int GetInt()
	    {
	    	return 1;
		}
};
class CBString:public CBase
{
	public:
		char *GetName()
		{
			return "CBString";
		}
		char *GetString()
		{
			return "Hello";
		}
};
int main()
{
	CBase *B1=(CBase*)new CBint();
	printf(B1->GetName());
	CBint *B2=static_cast<CBint*>(B1);//静态转换 
	if(B2)
	{
		printf("%d",B2->GetInt());
	}
	CBase *C1=(CBase*)new CBString();
	printf(C1->GetName());
	CBString *C2=static_cast<CBString*>(C1);
	if(C2)
	{
		printf(C2->GetString());
	}
	return 0;
}

从上面的代码可以看出,基类CBase的指针B1和C1分别指向了CBint类和CBString类的对象,并且在程序运行时基类通过ststic_cast进行了转换,这样就形成了一个运行时类型识别的过程。

二.RTTI与引用

RTTI必须能与引用一起工作。指针与引用存在明显不同,因为引用总是由编译器逆向引用,而一个指针的类型或他指向的类型可能要检测,例如,下面代码定义了一个子类和一个基类。

class CB
{
	public:
		int GetInt(){return 1;}
};
class Cl:public CB
{
};

通过下面的代码可以看出,typeid()获取的指针是基类类型,而不是子类类型或派生类类型,typeid()获取的引用是子类类型。

class CB
{
	public:
		int GetInt(){return 1;}
};
class Cl:public CB
{
};
int main()
{
	CB *p=new Cl();
	CB &t=*p;
	if(typeid(p)=typeid(CB*))
	{
		printf("指针类型是基类类型\n"); 
	}
	if(typeid(p)!=typeid(Cl*))
	{
		printf("指针类型不是子类类型\n");
	} 
	if(typeid(t)==typeid(CB))
	{
		printf("引用类型是基类类型\n"); 
	}
	return 0;
}

指针指向的类型在typeid()看来是派生类而不是基类,而用一个引用的地址产生的是基类而不是派生类。

三.RTTI与多重继承

RTTI具有非常强大的功能,对于面向对象的编程方法,如果在类继承时使用了virtual虚基类,RTTI仍可以准确的获取对象在运行时的信息。

例如,下面的代码通过虚基类的形式继承了父类,通过RTTI获取对象指针对象的信息。

class CB()
{
	virtual void dowork();
};
class CD1:virtual public CB
{
};
class CD2:virtual public CB
{
};
class CD3:public CD1,public CD2
{
	public:
		char *Print(){
			return "Hello";
		}
};
int main()
{
	CB *p=new CD3();//向上转型
	cout<<typeid(*p).name()<<endl;//获取指针信息 
	CD3 *pd3=dynamic_cast<CD3*>(p);
	if(pd3)
	cout<<pd3->Print()<<endl;
	return 0; 
}

即使只提供一个virtual基类指针,typeid()也能准确地检测出实际对象的名字。用动态映射同样也会工作得很好,但编译器不允许试图用原来的方法强制映射:

CD3 *pd3=(CD3*)p //错误转换

编译器知道这不可能正确所以它要求用户使用动态映射。

四.RTTI映射语法

 无论什么时候使用类型映射,都是在打破类型系统。这实际上是在告诉编译器,即使知道一个对象的确切类型,还可以假定它是另外一种类型,这本身就是一件很危险的事情,也是一个容易发生错误的地方。

为了解决这种问题,C++用保留关键字dynamic_cast,const_cast,static_cast,reinterpret_cast提供了一个统一的映射语法。为需要进行动态映射时提供了可能。这意味着那些已有的映射语法已经被重载的太多,不能再支持任何其他的功能了。

dynamic_cast:用于安全的向下映射

例如,通过dynamic_cast实现基类指针的向下转型。

#include<bits/stdc++.h>
using namespace std;
class CBase
{
	public:
		virtual void Print(){
			cout<<"CBase"<<endl;
		}
};
class CChild:public CBase
{
	public:
		void Print()
		{
			cout<<"CChild"<<endl; 
		}
};
int main()
{
	CBase *p=new CChild();
	p->Print();
	CChild *d=dynamic_cast<CChild*>(p);
	d->Print();
	return 0;
}

const_cast:用于映射常量和变量

如果想把一个const转换为非const,就要用到const-cast。这是可以用const-cast的唯一转换,如果还有其他的转换牵扯进来,它必须分开来指定,否则会有一个编译错误。

例如,在常方法中修改成员变量和常量的值。

#include<bits/stdc++.h>
using namespace std;
class CX
{
	protected:
		int m_count;
	public:
		CX(){
			m_count=10;
		}	
		void f() const
		{
			{
				const_cast<CX*>(this)->m_count=8;
				cout<<m_count<<endl;
			}
		}
};
int main()
{
	CX *p=new CX();
	p->f();
	const int i=10;
	int *n=const_cast<int*>(&i);
	*n=5;
	cout<<*n<<endl;
	return 0;
}

static_cast:为了行为良好和行为较好使用的映射,如向上转型和类型自动转换。

例如,通过static_cast将子类指针向上转成基类指针。

#include<bits/stdc++.h>
using namespace std;
class CB
{
	public:
		 void print()
		{
			cout<<"class CB"<<endl;
		}
};
class CD:public CB
{
	public:
		void print()
		{
			cout<<"class CD"<<endl;
		}
};
int main()
{
	CD *p=new CD();
	p->print();
	CB *b=static_cast<CB*>(p);//向上转型
	b->print();
	return 0; 
}

reinterpret_cast:将某一类型映射回原有类型时使用 

例如,将整型转成字符型,再由reinterpret_cast转换回原类型。

#include<bits/stdc++.h>
using namespace std;
int main()
{
	int n=97;
	char p[4]={0};//定义与整型大小相同的字符数组
	p[0]=(char)n;
	cout<<p[0]<<endl;
	int *f= reinterpret_cast<int*>(&p);
	cout<<*f<<endl;
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值