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可以把原来只允许单向转换的场景变成允许双向转换,类似的情况有:

  1.  一般来说,可以把任意的数据类型指针赋值给void *指针 ,但是不能把 void *指针赋值给任意数据类型的指针, 如果使用了static_cast ,那么就可以实现把void * 赋值给 任意数据类型;
  2. 一般来说, 我们可以把int类型赋值给double类型,但是不能把 double类型的赋值给int类型 ,但如果使用了static_cast ,那么就可以把double类型赋值给int类型
  3. 一般来说,我们可以把一个枚举类型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

posted on 2022-09-27 14:20  斗战胜佛美猴王  阅读(812)  评论(0编辑  收藏  举报