c++之上下转型 [static_cast和dynamic_cast] 详解
一.引言
1.1 类继承关系图
从图中可以看出,派生类不仅有自己的方法和属性,同时它还包括从父类继承来的方法和属性。当我们从派生类向基类转换时(向上转换),不管用传统的c语言还是c++转换方式都可以百分百转换成功。但是可怕是向下转换类型,也就是我们从基类向派生类转换,向下转换后派生类自己的方法和属性丢失了,一旦我们去调用派生类的方法和属性那就糟糕了,这就是对类继承关系和内存分配理解不清晰导致的。好在c++增加了static_cast和dynamic_cast运用于继承关系类间的强制转化。
1.2 语法及使用方式
static_cast< new_type >(expression)//静态转换 dynamic_cast< new_type >(expression)//动态转换
备注:new_type为目标数据类型,expression为原始数据类型变量或者表达式。
二. static_cast关键字(编译时类型检查,非多态转换)
2.1 定义说明
static_cast相当于传统的C语言里的强制转换,该运算符把expression转换为new_type类型,用来强迫隐式转换如non-const对象转为const对象,编译时检查,用于非多态的转换,可以转换指针及其他,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法:
①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。
③把空指针转换成目标类型的空指针。
④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性
基本类型数据转换举例如下:
char a = 'a'; int b = static_cast<char>(a);//正确,将char型数据转换成int型数据 double *c = new double; void *d = static_cast<void*>(c);//正确,将double指针转换成void指针 int e = 10; const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据 const int g = 20; int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
类上行和下行转换:
class Base {}; class Derived : public Base {} Base* pB = new Base(); if(Derived* pD = static_cast<Derived*>(pB)) {}//下行转换是不安全的(坚决抵制这种方法) Derived* pD = new Derived(); if(Base* pB = static_cast<Base*>(pD)) {}//上行转换是安全的
2.2 总结(单向变双向):
转换合法规则, 以下条件只要满足任一条,则转换合法:
- type_name可被隐式转换为expression所属的类型 ;
- expression可被隐式转换为type_name所属的类型.
High 是一个基类 ,Low是 High类的一个派生类,有如下代码:
High bar; //基类 Low blow; //派生类 ... High *pb = static_cast<High *>(&blow); //blow是派生类,pb是基类的指针,因此这里是向上转换,合法【基类指针本身就可指向派生类指针】 Low *pl = static_cast<Low *>(&bar); //bar是基类,pl是派生类指针,因此是向下转换,合法【但是要注意向下转换虽合法,但不安全,因为向下转换,访问到成员变量可能会崩溃,一般使用动态转换】
为什么上述两个转换都是合法的呢?
其实原因就一条,我们都知道,在派生关系中, 基类的指针可以直接指向一个派生类的对象,这个过程不需要显式转换 ,因此结合上述的转换合法原则,我们来分析一下上述两句转换的合法性:
- High *pb = static_cast<High *>(&blow); // High *是 type_name ,&blow所属的类型是Low * , 因为 High* 可以被隐式的转换为Low* ,这符合转换合法原则的前种情况 " type_name可被隐式转换为expression所属的类型" ,因此转换合法;
- Low *pl = static_cast<Low *>(&bar); // Low *是 type_name ,&bar所属的类型是High * ,还是因为 High* 可以被隐式的转换为Low* ,这符合转换合法原则的后种情况 "expression可被隐式转换为type_name所属的类型",因此转换合法.
因此,我们可以看到static_cast可以把原来只允许单向转换的场景变成允许双向转换,类似的情况有:
- 一般来说,可以把任意的数据类型指针赋值给void *指针 ,但是不能把 void *指针赋值给任意数据类型的指针, 如果使用了static_cast ,那么就可以实现把void * 赋值给 任意数据类型;
- 一般来说, 我们可以把int类型赋值给double类型,但是不能把 double类型的赋值给int类型 ,但如果使用了static_cast ,那么就可以把double类型赋值给int类型
- 一般来说,我们可以把一个枚举类型enum直接赋值给int ,但是不能把int直接转换为enum,但如果使用了static_cast ,那么就可以实现这种转换
类似的情况,大家可以继续发散..总之 ,原则就一条 :
能不能合法转换,主要取决于type_name和expression之间要存在任一方向的隐式转换关系.
但是至于转换后的内容是否安全,这是开发需要自行保证的,编译器无法保证.
三.dynamic_cast详解【运行时类型检查,常用多态下行转换】
3.1 定义及作用
dynamic_cast主要用于类层次结构中父类和子类之间指针和引用的转换,由于具有运行时类型检查,因此可以保证下行转换的安全性,何为安全性?即转换成功就返回转换后的正确类型指针,如果转换失败,则返回NULL,之所以说static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL。
dynamic_cast转换方式主要有三种:
dynamic_cast< type* >(e) //type必须是一个类类型且必须是一个有效的指针,类中层次转换最常用的方式 dynamic_cast< type& >(e)//type必须是一个类类型且必须是一个左值 dynamic_cast< type&& >(e)//type必须是一个类类型且必须是一个右值
e的类型必须符合以下三个条件中的任何一个:
- e是type的公有派生类
- e是type的共有基类
- e是type的类型
转换结果:
- 如果一条dynamic_cast语句的转换目标是指针类型并且失败了,则结果为0。
- 如果转换目标是引用类型并且失败了,则dynamic_cast运算符将抛出一个std::bad_cast异常(该异常定义在typeinfo标准库头文件中)。
- e也可以是一个空指针,结果是所需类型的空指针。
- 【比较】static_cast在下行转换时不安全,是因为即使转换失败,它也不返回NULL
dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的【父类指针本身就可指向一个子类对象】;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
3.2 代码演示
class Base{//抽象基类 virtual void fun(){} }; class Derived:public Base{//派生类 }; //******************************** Base *P = new Derived(); Derived *pd1 = static_cast<Derived *>(P);//不建议该方式,可能存在转换不安全情况 Derived *pd2 = dynamic_cast<Derived *>(P);
转换结果:
- 如果 P指向的确实是子类对象,则dynamic_cast和static_cast都可以转换成功;
- 如果 P 指向的是父类对象时,static_cast转换不安全;
如果 P 指向的是父类对象时,static_cast转换在编译时不会报错,但也可以返回一个子类对象指针(假想),这样是不安全的,在运行时可能会有问题,因为子类中包含父类中没有的数据和函数成员,这里需要理解转换的字面意思,转换是什么?转换就是把对象从一种类型转换到另一种类型,如果这时用 pd1 去访问子类中有但父类中没有的成员,就会出现访问越界的错误,导致程序崩溃。而dynamic_cast由于具有运行时类型检查功能,它能检查P的类型,由于上述转换是不合理的,所以它返回NULL。
因此,在面向对象编程开发中,我们常常使用 dynamic_cast<Derived *>(p)语法环境来判断当前对象(一般父类基类对象)是否指向当前派生类:
1. 指针类型:
Base为包含至少一个虚函数的基类,Derived是Base的共有派生类,如果有一个指向Base的指针bp,我们可以在运行时将它转换成指向Derived的指针,代码如下:
if(Derived *dp = dynamic_cast<Derived *>(bp)){ //使用dp指向的Derived对象 } else{ //使用bp指向的Base对象 }
2.引用类型
因为不存在所谓空引用,所以引用类型的dynamic_cast转换与指针类型不同,在引用转换失败时,会抛出std::bad_cast异常,该异常定义在头文件typeinfo中。
void f(const Base &b){ try{ const Derived &d = dynamic_cast<const Base &>(b); //使用b引用的Derived对象 } catch(std::bad_cast){ //处理类型转换失败的情况 } }
四. 转换注意事项
C++中层次类型转换中无非两种:上行转换和下行转换
- 对于上行转换,static_cast和dynamic_cast效果一样,都安全;
- 对于下行转换:你必须确定要转换的数据确实是目标类型的数据,即需要注意要转换的父类类型指针是否真的指向子类对象,常用dynamic_cast进行转换。虽然static_cast和dynamic_cast都能成功,但是不安全,可能会出现访问越界错误,而dynamic_cast在运行时类型检查过程中,判定该过程不能转换,返回NULL。
另外,尽量少使用转型操作,尤其是dynamic_cast,因为是运行时检查耗时较高,开销必然不小,会导致性能的下降,尽量使用其他方法替代。
————————————————
参考链接:
链接:https://blog.csdn.net/u014624623/article/details/79837849
链接:https://blog.csdn.net/qq_26849233/article/details/62218385
性能耗时参考链接:https://blog.csdn.net/debugconsole/article/details/9379627?ops_request_misc=&request_id=&biz_id=102&utm_term=dynamic_cast%20%E6%80%A7%E8%83%BD%E4%B8%8B%E9%99%8D&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduweb~default-3-9379627.142^v73^pc_new_rank,201^v4^add_ask,239^v2^insert_chatgpt&spm=1018.2226.3001.4187