C++强制类型转换和RTTI

来源:互联网 发布:淘宝网房产司法拍卖 编辑:程序博客网 时间:2024/06/06 02:47

前言:C++提供了四种强制类型转换的关键字:const_cast,static_cast,reinterpret_cast和dynamic_cast,下面分别进行介绍。

const_cast操作符

const_cast<类型>(表达式),类型是指目标类型,表达式一般是需要转换的变量。//注意在const_cast中目标类型和表达式类型是相同的,只是去掉其const特性。

const_cast用于去除变量的const或volatile属性。即将常量指针转换为非常量指针,将常量引用转换为非常量引用。转换前后的量指向同一内存空间。

class Test {public:    Test() {}    Test(int _x,int _y=1, int _z=2) : x(_x),y(_y),z(_z)    {    }    ~Test(){}public:    int x;    int y;    int z;};const Test* pTest=new Test(1,2,3);Test* pA=const_cast<Test*>(pTest);//修改指针为非常量pA->x=2;cout<<pA->x<<endl;//输出2cout<<pTest->x<<endl;//输出2const Test TestOri(1,2,3);Test& TestReslut=const_cast<Test&>(TestOri);//修改引用为非常量引用TestReslut.y=4;cout<<TestReslut.y<<endl;//输出4cout<<TestOri.y<<endl;//输出4

static_cast

static_cast<类型>(表达式),编译器进行静态的类型转换,不作运行时类型检查。

能够对于内置的数据类型进行相互转换,对于有继承关系的类也可以相互转换,即可以在继承体系内将类进行相互转换,但是不能转换为其它类。

class ClassA{    public:    virtual ~ ClassA(){}    virtual void FunctionA(){}};class ClassB{    public:    virtual void FunctionB() {}};class ClassC: public ClassA, public ClassB{    public:};ClassC aObject;//子类对象ClassA *pA = &aObject;ClassB *pB = &aObject;ClassC *pC = &aObject;

1.父类和子类的指针,即有继承关系的类可以通过static_cast进行强制类型转换。示例如下:

ClassC *pC=static_cast<ClassC*>(pA);//将父类的指针转换成子类ClassA *pA=static_cast<ClassA*>(pC);//将子类的指针转换成父类

2.可以将任意指针转换成空类型的指针

void *pVoid=static_cast<void*>(pA);//将其它指针转换成void类型的指针

3.将void类型的指针转换成其它类型的指针

ClassA *pA=static_cast<ClassA*>(pVoid);//将空类型的指针转换成其它类型指针

不能将两个没有关系的类进行相互转换,否则编译器会报错:

“Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast”

reinterpret_cast

reinterpret_cast<类型>(表达式),将类型转换为没有关联的类型,不补足bit位,不安全。

reinterpret_cast将一种类型转换为另外一种没有关联的类型,不作任何检查,两个类型所占内存大小不同时,不补足bit位,如将char*转换为int*等。

class TestOther Other;class Test* pTest=reinterpret_cast<Test*>(&Other);//无关联转换

注意在C++中两个毫不相关的类指针之间使用圆括号进行显式的强制类型转换是可以的,效果和reinterpret_cast相同:

class Test1{};class Test2{};Test1* pTest1;Test2* pTest2=(Test2*)pTest1;//显式强制类型转换,OK,效果和reinterpret_cast相同

reinterpret_cast也不是万能的,可以将指针类型转换为足以容纳4个字节的整型,但是不能将指针类型转换为更小的整型或者浮点型。另外,不能将函数指针和数据指针相互转换,会出现下面的编译错误:

cannot convert from 'void *(__cdecl *)(int)' to 'int *'

dynamic_cast

The dynamic_cast keyword casts a datum from one pointer or reference
type to another, performing a runtime check to ensure the validity of the cast.

static_cast可以将两个有继承关系的类进行相互转换,子类可以转换为父类,父类也可以转换为子类,对于这种转换是否能够实现不进行运行时检查。而dynamic_cast则会在运行期进行检查这种转换能否发生。如果不能够发生,返回空指针。

在将子类转换为父类的时候,static_cast和dynamic_cast是相似的。但是在将父类转换为子类的时候,dynamic_cast需要进行执行期检查,执行期类型信息在虚函数表中,所以要求父类必须是多态类。

(1)如果父类没有虚函数,那么编译过不了,所以父类必须有虚函数才能过了编译关,但是不代表就能转换成功,示例如下:

class A //没有虚函数{public:      A() {}      ~A() {} private:    int a;};class Derived : public A{  public:      Derived() {}      ~Derived() {}  private:      char bit3 ;    int b;};  A* pANew=new A;Derived* pDerivedNew=dynamic_cast<Derived*>(pANew);//编译报错cout<<pDerivedNew<<endl;//编译关过不了,提示A类不是多态类,因为没有虚函数,执行期类型信息在虚函数表中。

(2)如果父类有虚函数,那么编译关就能过了,但是不代表能够转换成功:

class A{public:      A() {}      ~A() {} private:    int a;    virtual void test() {}};A* pANew=new A;//父类指针指向父类对象Derived* pDerivedNew=dynamic_cast<Derived*>(pANew);//编译Okcout<<pDerivedNew<<endl;//输出NULL,转换失败A* pA=new Derived;//父类指针指向子类对象Derived* pDerivedNew=dynamic_cast<Derived*>(pANew);//编译Okcout<<pDerivedNew<<endl;//输出不为空,转换成功

总结如下:

(1)子类转换为父类,多态本身就支持,dynamic_cast当然可以;(2)父类转换为子类,要求父类必须要有虚函数,并且是多态情况下(父类指针指向子类对象);(3)横向转换

横向转换的示例如下:

class Derived : public A,public Base //两个必须都是public才行,其它组合都不行{  public:      Derived() {}      ~Derived() {}  private:      char bit3 ;};  A*pA=new A;Base* pBase=dynamic_cast<Base*>(pA);cout<<pBase<<endl;//结果为空,转换失败A* pA=new Derived;//父类的指针指向子类的对象,要求必须是public继承Base* pBase=dynamic_cast<Base*>(pA);cout<<pBase<<endl;//结果不为空,转换成功

也就是说,必须是一个父类的指针指向子类的对象,才可以将该指针转换为另一个父类。

(3)两个不相关的类进行相互转换,会返回NULL

A* pANew=new A;B* pB=dynamic_cast<B*>(pANew);//A要含有虚函数,否则报错cout<<pB<<endl;//返回NULL

注意,如果指针的转换失败会返回NULL,如果引用的转换失败会抛出bad_cast异常

综上所述,dynamic_cast转换成功的必要条件:

(1)父类是多态类,即有虚函数表;(编译期需要)(2)多态情况下,即父类的指针指向的是子类的对象;(运行期需要)

运行时类型检查RTTI

运行时类型检查是指在运行阶段确定指针或者引用所指对象的实际类型,因为C++具有多态性,假如一个基类有多个派生类,现在有一个基类的指针,我们无法确定该基类的指针是指向基类对象还是哪个子类对象。

RTTI只适用于多态类,即有虚函数表的类。

C++中有三个支持RTTI的元素:

1.dynamic_cast,动态类型转换,在前面已经介绍过了;2.type_id关键字,指出一个对象的类型(type_info);3.type_info结构存储有关类型的信息;

我们主要介绍type_id关键字:

class Test {};//假设含有虚函数class Derived : public Test{};Test* pTest=new Test;Test* pDerived=new Derived;//父类的指针指向子类的对象type_id(Test)==type_id(pTest);//返回bool值,truetype_id(Test)==type_id(pDerived);//返回bool值,false

如果type_id接受一个空指针,由于需要访问该类的虚函数表,所以会抛出异常bad_typeid.该异常类型是从exception类派生而来;

当我们需要获取该类的名称的时候,我们可以使用type_id(指针或引用).name()

RTTI会带来额外的开销,大部分编译器都支持RTTI,但在默认的情况下是关闭的,需要我们手动在设置里面打开。

class type_info{public:    virtual ~type_info();    bool operator==(const type_info& _Rhs) const; // 用于比较两个对象的类型是否相等    bool operator!=(const type_info& _Rhs) const; // 用于比较两个对象的类型是否不相等    bool before(const type_info& _Rhs) const;    // 返回对象的类型名字,这个函数用的很多    const char* name(__type_info_node* __ptype_info_node = &__type_info_root_node) const;    const char* raw_name() const;private:    void *_M_data;    char _M_d_name[1];    type_info(const type_info& _Rhs);//拷贝构造函数    type_info& operator=(const type_info& _Rhs);//赋值运算符函数    static const char * _Name_base(const type_info *,__type_info_node* __ptype_info_node);    static void _Type_info_dtor(type_info *);};

type_info类的声明如上所示,其中拷贝构造函数和赋值运算符函数都是private,那么type_id关键字是如何返回一个type_info类型的引用呢?答案是采用友元技术。

RTTI是怎么样获取对象的类型的呢?

在每个类对象的虚函数表中,slot0存放了该类的type_info对象,RTTI在执行期获取虚函数表中的type_info对象,通过type_info.name()函数获取类名称。具体可以参考《深度探索C++对象模型》一书。

0 0