【问题标题】:C++ type comparison: typeid vs double dispatch dynamic_castC++ 类型比较:typeid 与双调度 dynamic_cast
【发布时间】:2016-02-12 19:55:42
【问题描述】:

是否有任何性能或稳健性的理由偏爱其中一个?

#include <iostream>
#include <typeinfo>

struct B
{
    virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
    virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};

struct D0 : B
{
    virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
    virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};

struct D1 : B
{
    virtual bool IsType(B const * b) const { return IsType2nd(b) && b->IsType2nd(this); }
    virtual bool IsType2nd(B const * b) const { return dynamic_cast<decltype(this)>(b) != nullptr; }
};

int main()
{
    using namespace std;
    B b, bb;
    D0 d0, dd0;
    D1 d1, dd1;

    cout << "type B  == type B  : " << (b.IsType(&bb)   ? "true " : "false") << endl;
    cout << "type B  == type D0 : " << (b.IsType(&dd0)  ? "true " : "false") << endl;
    cout << "type B  == type D1 : " << (b.IsType(&dd1)  ? "true " : "false") << endl;
    cout << "type D0 == type B  : " << (d0.IsType(&bb)  ? "true " : "false") << endl;
    cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
    cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
    cout << "type D1 == type B  : " << (d1.IsType(&bb)  ? "true " : "false") << endl;
    cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
    cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
    cout << endl;
    cout << "type B  == type B  : " << (typeid(b) == typeid(bb)   ? "true " : "false") << endl;
    cout << "type B  == type D0 : " << (typeid(b) == typeid(dd0)  ? "true " : "false") << endl;
    cout << "type B  == type D1 : " << (typeid(b) == typeid(dd1)  ? "true " : "false") << endl;
    cout << "type D0 == type B  : " << (typeid(d0) == typeid(&bb) ? "true " : "false") << endl;
    cout << "type D0 == type D0 : " << (typeid(d0) == typeid(dd0) ? "true " : "false") << endl;
    cout << "type D0 == type D1 : " << (typeid(d0) == typeid(dd1) ? "true " : "false") << endl;
    cout << "type D1 == type B  : " << (typeid(d1) == typeid(bb)  ? "true " : "false") << endl;
    cout << "type D1 == type D0 : " << (typeid(d1) == typeid(dd0) ? "true " : "false") << endl;
    cout << "type D1 == type D1 : " << (typeid(d1) == typeid(dd1) ? "true " : "false") << endl;
}

输出:

type B  == type B  : true 
type B  == type D0 : false
type B  == type D1 : false
type D0 == type B  : false
type D0 == type D0 : true 
type D0 == type D1 : false
type D1 == type B  : false
type D1 == type D0 : false
type D1 == type D1 : true 

type B  == type B  : true 
type B  == type D0 : false
type B  == type D1 : false
type D0 == type B  : false
type D0 == type D0 : true 
type D0 == type D1 : false
type D1 == type B  : false
type D1 == type D0 : false
type D1 == type D1 : true 

【问题讨论】:

  • 根据我的经验,任何试图找出多态类的实际类型都表明存在设计缺陷。
  • @SergeyA,同意了。我继承了到处使用类型开关的代码,我正在尝试改进它。
  • 您是否也接受第三个选项,或者您只对上述两个选项感兴趣?
  • @skypjack,第三个选项是什么?
  • @ThomasMcLeod 抱歉,如果我迟到了,我一直很忙。第三个选项取决于您的要求。是不是如示例中的 1 级层次结构?你能在你真正的问题中实例化B吗?

标签: c++ dynamic-cast typeid double-dispatch


【解决方案1】:

从设计的角度来看,双重调度要灵活得多:

  • 目前您检查与IsType2nd(b) &amp;&amp; b-&gt;IsType2nd(this) 的类型之间的严格相等性。但可能在某些时候你想进一步推导

  • 但是有一天您可能想要进一步派生 D1,但在比较类型时仍想将其视为 D1 对象。这种特殊情况很容易通过双重调度完成。

这种灵活性是有代价的:汇编代码将通过 vtable 使用 2 次间接调用,外加一个动态转换。

直接类型信息并不是最好的设计,正如 Sergey 指出的那样:它始终是严格的类型比较,不可能有特殊情况。

这种不灵活性带来了代码生成简单的优势:代码只需在 vtable 的开头获取动态类型信息(并且编译器可以轻松地针对编译时已知类型的对象优化此提取.

为了好奇,这里some code generated :他的 typeid 在编译时被优化掉了,而双重调度仍然依赖于间接调用。

【讨论】:

    【解决方案2】:

    如 cmets 所述,它遵循另一种既不使用 typeid 也不依赖 dynamic_cast 的可能解决方案。

    我添加了几个额外的示例来展示如何轻松定义 family 类型(例如,D1D1Bis 都显示为同一个系列类型,即使它们实际上是不同的类型)。
    无论如何,不​​确定这是一个理想的功能......

    希望你会感兴趣。

    #include<iostream>
    
    struct BB {
        virtual unsigned int GetType() = 0;
    
        bool IsType(BB *other) {
            return GetType() == other->GetType();
        }
    
    protected:
        static unsigned int bbType;
    };
    
    unsigned int BB::bbType = 0;
    
    struct B: public BB {
        unsigned int GetType() override {
            static unsigned int bType = BB::bbType++;
            return bType;
        }
    };
    
    struct D0: public B {
        unsigned int GetType() override {
            static unsigned int d0Type = BB::bbType++;
            return d0Type;
        }
    };
    
    struct D1: public B {
        unsigned int GetType() override {
            static unsigned int d1Type = BB::bbType++;
            return d1Type;
        }
    };
    
    struct D1Bis: public D1 { };
    
    int main() {
        using namespace std;
        B b, bb;
        D0 d0, dd0;
        D1 d1, dd1;
        D1Bis d1Bis;
    
        cout << "type B  == type B  : " << (b.IsType(&bb)   ? "true " : "false") << endl;
        cout << "type B  == type D0 : " << (b.IsType(&dd0)  ? "true " : "false") << endl;
        cout << "type B  == type D1 : " << (b.IsType(&dd1)  ? "true " : "false") << endl;
        cout << "type B  == type D1BIS : " << (b.IsType(&d1Bis)  ? "true " : "false") << endl;
        cout << "type D0 == type B  : " << (d0.IsType(&bb)  ? "true " : "false") << endl;
        cout << "type D0 == type D0 : " << (d0.IsType(&dd0) ? "true " : "false") << endl;
        cout << "type D0 == type D1 : " << (d0.IsType(&dd1) ? "true " : "false") << endl;
        cout << "type D0 == type D1BIS : " << (d0.IsType(&d1Bis) ? "true " : "false") << endl;
        cout << "type D1 == type B  : " << (d1.IsType(&bb)  ? "true " : "false") << endl;
        cout << "type D1 == type D0 : " << (d1.IsType(&dd0) ? "true " : "false") << endl;
        cout << "type D1 == type D1 : " << (d1.IsType(&dd1) ? "true " : "false") << endl;
        cout << "type D1 == type D1Bis : " << (d1.IsType(&d1Bis) ? "true " : "false") << endl;
    }
    

    【讨论】:

    • 这基本上是C语言时代常见的类型枚举方案。
    • 嗯,这在今天仍在使用,通常与 CRTP 成语一起使用,为派生类提供类型(例如,entityx 做了类似的事情),但它没有t 非常适合具有多个级别的层次结构,所以...我尝试根据您的问题调整解决方案。
    • CRTP不需要,因为可以通过模板类型推导发现基类的模板参数。
    • 通常并不意味着必然。无论如何,我谈到了第三种可行的选择,这个可行,请随意不要使用它。 :-)
    猜你喜欢
    • 1970-01-01
    • 2021-08-04
    • 1970-01-01
    • 2015-12-17
    • 1970-01-01
    • 1970-01-01
    • 2018-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多