第13篇:C++多态中的Downcast操作

上一篇我们介绍了与动态绑定伴随的upcast类型转换,这是一种符合类型安全的类型转换操作。本篇还将介绍有关downcast的类型转换操作,但在此之前,如果你还不熟悉C++的类型系统,可以参考微软相关的开发文档。因为类型系统与类型转换有关,这是我们在C++中的类型转换的一种方式。

当你打算了解动态类型转换的这个话题,我相信你已经知道C++其他类型转换的方法,例如static_cast<Type>或const_cast<Type>.但是dynamic_cast<Type>仅对C++起作用,在C中无效。

让我们回到上一篇的例子,UML如下图


std::string m_name="经理";
Manager* m=new Manager(m_name);

事实上,m1具备两种类型,它既是Employee类型也是Manager类型,它更像类型列表一样。那么它可以轻松将其转换为Employee类型是非常轻松的。但我们关注的是首先派生类Manager实例隐式转换转换为基类Employee实例e1,然后再次转换回Manager类型的实例。

Employee e1=m;
Manager* m1=e1; //非法操作

当然C++编译器是不允许你这么做的,至于这种错误的做法我们在前文已经说过了。

错误的示例:显式的C风格强制类型转换

Manager* m2=(Manager*)e1;//危险操作

这种做法,C++不会报错但这是非常危险的行为,前文已经分析过会造成错误的内存访问和垃圾数据

错误的内存访问和垃圾数据

下文我们需要引入一种安全的downcast操作,那么动态类型转换(dynamic_cast)就是本文谈论的主题。
destType* dstObj=dynamic_cast<destType*>(src)

  • 如果运行时src和destType所引用的对象,是相同类型,或者存在is-a关系(public继承)则转换成功;否则转换失败。
  • dynamic_cast只能用来转换多态类型(即定义了虚函数)的对象的指针或引用。如果操作数是指针,成功则返回目标类型的指针,失败返回nullptr。
  • 如果操作数是引用,成功则返回目标类型的引用,失败抛出std::bad_cast异常。

dynamic_cast的“运行时类型的转换匹配”,是通过维护一棵由type_info类型对象作为节点的类型继承关系的树,遍历这棵继承树来确定一个待转换的对象的类型和目标类型之间是否存在is-a关系。type_info类型对象是运行时类型信息的一部分。---摘录自维基百科

那么,是否使用动态转换完全取决于你,但本文只是从一个客观的角度论述使用dynamic_cast<Type>基本运行原理。更重要的是,它不仅像编译时那样进行强制转换,还可以在运行时进行类型转换的评估。因此动态类型会在底层执行一些额外的操作,所以具有一定运行时的开销。而dynamic_cast专用于继承层次结构中的类型转换尤其是类型向下转换(downcast)从基类类型转换成派生类型

例如:我们只想将一个Manager对象强制转换回一个Employee对象,那么他们可以从Employee类中派生出来,其实很简单,Manager对象已经具有Employee类型,因此可以隐式完成,无需强制转换。尽管如此,仍然可以使用dynamic_cast<Type>进行upcast操作,但显示有点画蛇添足。

另外还有一种复杂的情况,假设我们有一个Employee实例,并且我们想将其转换为Manager对象,据我们所知,它只是Employee类型的指针,我们无法知道它是否为一个Manager对象。dynamic_cast<destType>可以通过运行时类信息查找将一个Employee实例转换为Manager实例,若转换失败将返回一个nullptr,那么使用此返回来验证我们是否在执行下一步操作。

Manager* m=new Employee() //?

dynamic_cast的适用时机

示例1:基类对象到派生类的转换

  • 首先我们创建了一个Manager类的实例m.
  • 然后执行了upcast操作后得到一个Employee类的实例副本e1
 int main(void){
    std::string e_name="职员";
    std::string m_name="经理";
 
    Manager *m=new Manager(m_name);
  
    Employee *e1=m;
    Manager *m1=dynamic_cast<Manager*>(e1);
    std::cout<<"原版Manager实例:sizeof "<<sizeof(*m)<<std::endl;
    std::cout<<"副本Manager实例:sizeof "<<sizeof(*m1)<<std::endl;
    return 0;
 }
  • 最后我们通过dynamic_cast如上面例子的示例代码,我们仍然有效地得到一个Manager类实例副本,此次操作是从基类实例到派生类实例的转换,我们叫downcast操作,下图是dynamic_cast前后的虚表的条目是一致的。

反例2:同层次的派生类对象到派生类的转换

int main(void){
    std::string e_name="职员";
    std::string m_name="经理";
 
    Manager *m=new Manager(m_name);
    Supervisor* s=new Supervisor(e_name);
  
    Manager *m1=dynamic_cast<Manager*>(s);
    
    if(m1==nullptr){
        std::cout<<"转换失败"<<std::end;
    }
    return 0;
 }

其实很明显,同层次的派生类之间是无法转换的,dynamic_cast返回nullptr指针表示转换失败

那么,从上面的两个示例中,你应该发现一个问题.编译器是如何知道实例e1就能成功转换为Mananager实例?又如何知道我们Supervisor类实例无法转换为Manager类实例?它实际上存储运行时的类型信息,即所谓的RTTI

运行期类型信息(Runtime type information,RTTI)指的是在程序运行时保存其对象的类型信息的行为。某些语言实现仅保留有限的类型信息,例如[继承树]信息,而某些实现会保留较多信息,例如对象的属性及方法信息。

这确实增加了开销。但是RTTI可以确保进行类型转换(包含隐式转换和动态类型转换)之类的操作可以安全地进行。

你需要考虑一些性能上的问题。因为运行时的类型存储有关自身的信息,而不是其他类型的信息,并且大规模的动态转换需要相当的时间消耗。dynamic_cast内部实际上会根据RTTI检测目标操作对象的类型信息和用户期待的类型是否匹配,都必须在运行时随时进行验证的。

若你还记得,第10篇《C++继承中虚表的内存布局》已经简单提过类型信息有关概念。除非你有特殊需求,没必要深究C++底层类继承的实现。

小结:

我们为什么在阐述动态绑定中绕了一大圈的原因,大概你们都应该清楚了。因为在类继承过程中,动态绑定通常会涉及类型转换中upcast操作和downcast操作。而这两种类型转换操作中,会涉及一个成员方法(包含虚成员函数)和属性可见性问题。这些问题,在本篇和上一篇我已经说的很清楚了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容