文章摘自 C++从入门到精通(第四版)清华大学出版社
目录
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;
}