C++ type comparison: typeid vs double dispatch dynamic_cast

时间:2016-02-12 19:55:42

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

Are there any performance or robustness reasons to prefer one over the other?

#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;
}

output:

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 

2 个答案:

答案 0 :(得分:1)

From the design perspective, the double dispatch is far more flexible:

  • Currently you check for strict egality between the type with private void KeysDown(object sender, KeyEventArgs e) { if (Keyboard.IsKeyDown(Keys.Return)) { //do something } } . But may be at some time you'd like to derivate further

  • But one day you may want to derivate further D1, but still want to consider it as if it where a D1 object when comparing types. This kind of special case is easily done with double dispatch.

This flexibility comes at a cost: the assembler code would use 2 indirect calls via a vtable, plus a dynamic cast.

The direct type information is not the greatest design, as Sergey pointed out: It will always be a strict type comparison, with no special case possible.

This inflexibility comes with advantage of simplicity in code generation: the code has just to get the dynamic type information at the start of the vtable (and the compiler can easily optimise this fetch away for object where the type is known at compile time.

For the sake of curiosity, here some code generated : he typeid is optimised away at compile time, whereas the double displatch still relies on indirect calls.

答案 1 :(得分:0)

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

我已添加了几个其他示例,以说明如何轻松定义系列类型(例如,此处出现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;
}