文档章节

为什么说不要使用 dynamic_cast, 以及如何避免使用?

晚睡综合症
 晚睡综合症
发布于 2015/06/11 22:21
字数 1294
阅读 527
收藏 0
C++

阿里云携手百名商业领袖、技术大咖,带您一探行进中的数字新基建!>>>

首先,C++ 的RTTI(包括了 dynamic_cast)肯定不是个很好的设计:

  • dynamic_cast 是有可能抛出 std::bad_cast 异常的,但大多数时候,我们不希望使用 C++ 异常系统,理由嘛,多种多样,我的原因是——我就根本没学会用异常这个技术。而且 C++ 异常系统是没有 finally 关键字的,很别扭。

  • C++ 的 RTTI 整体上比较弱(比如无法枚举成员函数),几乎就等于不能用,真要用类型信息的话,很多项目选择自己实现。

  • dynamic_cast 性能依赖于编译器,如果是通过字符串比较实现的,性能堪忧,尤其是继承树庞大的时候,dynamic_cast 可能需要执行若干次字符串比较。当然实际上我们很少需要如此关心性能。

  • 跟大多数语言不一样,由于多继承的存在,C++ 的类型转换可能会改变指针的值,你大可以想象这可能造成多么吊诡的错误。


进而,滥用 dynamic_cast 会带来一些问题,比如:

  • 假如你到处使用 dynamic_cast 确认具体类型,那么当你要增加一个子类的时候,你得修改多少地方?你不嫌麻烦吗?不怕漏下了吗?

  • 使用 dynamic_cast 的代码是难于测试的,你无法通过接口确认它到底依赖于哪些具体类,测试代码会比较复杂。并且增加了子类就要修改测试代码。


再进而,大多数时候都是滥用:

  • C++ 为了实现上层代码尽量不要关心具体类型,特意设计了重载、多态、模版等特性,你不用,非要自己写代码处理,那你为啥要用 C++ 呢?

  • 很多时候我们只是要知道对象的某种性质(比如常见的 xxx type)而不是全部类型信息,使用 dynamic_cast 获得了全部类型信息,继而要 include 具体类的头文件,不符合“最小”原则。


再再进而,大多数 dynamic_cast 都可以修改代码去掉,至少可以尽量下压到底层,或者集中到一起方便维护。比如:

  • 《设计模式》那本书里有一个工厂方法模式,可以用来解决一些问题,比如:

// 带有 dynamic_cast
void some(Base *p) {
    Derived *pd = dynamic_cast<Derived *>(p);
    Partner *ptnr = nullptr;
    if (pd) {
        ptnr = new Partner4Derived(pd);
    }
    // ...
}
// 不带 dynamic_cast
void some(Base *p) {
    Partner *ptnr = p->getPartner(); // 返回Partner4Derived
    // ...

可以通过函数重载去掉 dynamic_cast,比如这样:

class Life : public GameObject {
public:
    // ...
    virtual void kill(Life *other) = 0;
    virtual void killedBy(Player *other) = 0;
    virtual void killedBy(Monster *other) = 0;
    virtual void killedBy(Elf *other) = 0;
    // ...
};
class Player : public Life {
public:
    // ...
    virtual void kill(Life *other) {
        other->killedBy(this);
    }
};
class Elf : public Life {
    //...
};
class Monster : public Life {
public:
    // ...
    // 实现你的 PlayerKillMonster
    virtual void killedBy(Player *other) {
        // ...
    }
    // ...
};
void gameObjectKillTarget(GameObject *killer, GameObject *target) {
    // ...
    killer->kill(target); // kill 是虚函数
}

这种实现有个挺洋气的名字叫做“double-dispatch”,或者Visitor模式,其实也很别扭,但是确实可以去掉上层调用代码中的 dynamic_cast。

但是,你自己也做项目你知道的,拿着《设计模式》往项目里套,往往对不上号,C++ 既然打了 dynamic_cast 这个补丁,说明还是能用到的,我说几种个人认为比较适合用 dynamic_cast 处理的情况(注意,从这里开始我就不确定正确性了,只是个人看法,大家觉得不对可以在评论里留言纠正): 

  • 处理参数协变问题。C++ 的虚函数返回值是可以跟着 this 协变的,但是参数不行。所以会有这样的写法(google 规范文档里的例子):

    bool Base::equal(Base *other) = 0;
    
    bool Derived::equal(Base *other) {
        Derived *p = dynamic_cast<Derived *>(other);
        if (!p) return false;
        // ...
    }

    这里要想去掉 dynamic_cast 当然是可以去掉的,但是 Derived::equal 这个函数,只是利用转型看看 other 和 this 的类型是不是一样,不依赖别的具体类,这样写似乎也没什么问题。

  • dynamic_cast 只是一个具体实现方式,本质上是一个“判断对象类型并做相应处理”的问题。一段代码完成的工作,总是要做的,不是放在 A 处做,就是放在 B 处做。假设我们想尽量保持 B 的单纯,那么这项工作就可以在 A 处,反之亦然。比如类似这样:

    bool Object::event(Event *e) {
        switch (e->type()) {
        case Event::Timer:
            timerEvent((TimerEvent*)e);
            break;
    
        case Event::ChildAdded:
        case Event::ChildPolished:
        case Event::ChildRemoved:
            childEvent((ChildEvent*)e);
            break;
        // ...
    }

    这里的强制转型和 dynamic_cast 其实是个差不多的东西,想要去掉当然是可以去掉的,方法类似于上面的方法二,但是那样会让 Event 变得比较复杂。当我们希望它只是一个简单的属性集,不依赖于 Object 及其任何子类,就会有这样的实现。Object 和 Event 都有大量的子类,并且和你给出的例子不同的是,子类随时可能大量增加,并且和基类不在一个模块(module)中,在这种情况下,所谓的“优雅解决”很可能是绣花枕头,能不用就别用。


© 著作权归作者所有

晚睡综合症
粉丝 0
博文 21
码字总数 10654
作品 0
杭州
程序员
私信 提问
加载中

评论(0)

【转载】When should static_cast, dynamic_cast and reinterpret_cast be used?

这是我偶然在 http://stackoverflow.com/questions/ 网页上发现的一个问题(类似博客园的博问),问题主要是关于询问应该怎样使用,以及何时使用C++里面的这几种类型转换操作符:staticcase,...

hoodlum1980
2009/05/11
0
0
static_cast与dynamic_cast转换

一 C语言中存在着两种类型转换: 隐式转换和显式转换 隐式转换:不同数据类型之间赋值和运算,函数调用传递参数……编译器完成 char ch; int i = ch; 显示转换:在类型前增加 :(Type)变量...

j_m
2012/06/27
205
0
C++的强制类型转换

c/c++强制类型转换 Q:什么是C风格转换?什么是staticcast, dynamiccast 以及 reinterpret_cast?区别是什么?为什么要注意? A:转换的含义是通过改变一个变量的类型为别的类型从而改变该变量...

ucliaohh
2016/10/26
5
0
[从C到C++] 1.7 C++ 强制类型转换

[toc] 在C++语言中新增了四个关键字staticcast、constcast、reinterpretcast和dynamiccast。这四个关键字都是用于强制类型转换的。我们逐一来介绍这四个关键字。 1) static_cast 在C++语言中...

wu_being
2018/06/16
0
0
C++类型转换:C++风格: static_cast、dynamic_cast、reinterpre

首先回顾一下C++类型转换: C++类型转换分为:隐式类型转换和显式类型转换 第1部分. 隐式类型转换 又称为“标准转换”,包括以下几种情况: 1) 算术转换(Arithmetic conversion) : 在混合类型...

Caishu
2016/04/14
51
0

没有更多内容

加载失败,请刷新页面

加载更多

.NET中小数,浮点数和双精度之间的区别? - Difference between decimal, float and double in .NET?

问题: What is the difference between decimal , float and double in .NET? .NET中的decimal , float和double float什么区别? When would someone use one of these? 有人什么时候会使用......

fyin1314
今天
22
0
如何找出Windows上正在侦听端口的进程? - How can you find out which process is listening on a port on Windows?

问题: 如何找出Windows上正在侦听端口的进程? 解决方案: 参考一: https://stackoom.com/question/CXO/如何找出Windows上正在侦听端口的进程 参考二: https://oldbug.net/q/CXO/How-can...

技术盛宴
今天
10
0
OSChina 周三乱弹 —— 一家动物都快饿成标本了~

@黑觉非常君 :前天晚上9点开始睡觉,睡到昨天上午8点起床,昨天下午2点又睡,睡到下午7点多,晚上10点又困了,又睡,睡到今天上午8点,中途没醒过,怎么这么能睡,是不是快挂了。 能睡不是好...

小小编辑
今天
15
0
神剧推荐全剧最污片段精剪

神剧推荐,全剧最污片段精剪 豆瓣评分最高,脑洞最大,脑回路最曲折,恶搞无数经典,没有一条差评的神剧 整个系列完整版 到这里观看

a57571735
今天
22
0
pingan.

职位诱惑:金融,技术研发,技术攻关,大舞台大作为职位描述:职位诱惑:立志打造一支国内一流实盘量化交易框架开发团队职位描述:职位描述:负责实盘低时延量化交易框架及附属行...

MtrS
今天
15
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部