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

首先,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)中,在这种情况下,所谓的“优雅解决”很可能是绣花枕头,能不用就别用。

来自:https://my.oschina.net/legendlee/blog/465797

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

推荐阅读更多精彩内容