2016-02-12 12 views
4

Есть ли какие-либо соображения производительности или надежности, чтобы предпочесть друг другу?Сравнение типов C++: typeid vs double dispatch dynamic_cast

#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 
+0

По моему опыту любая попытка выяснить фактический тип полиморфного класса указывает на дефект конструкции. – SergeyA

+0

@SergeyA, согласовано. Я унаследовал код, который использует переключатель типа везде, и я пытаюсь его улучшить. – ThomasMcLeod

+0

Вы также принимаете третий вариант или вас интересуют только два из них? – skypjack

ответ

1

С точки зрения дизайна, двойной доставки гораздо более гибок:

  • В настоящее время вы проверяете строгого Эгалитарный между типом с IsType2nd(b) && b->IsType2nd(this). Но может быть в какой-то момент, когда вы хотели бы дополнительно дерифицировать

  • Но в один прекрасный день вы можете захотеть получить дальнейший D1, но все же хотите считать, что это объект D1 при сравнении типов. Этот особый случай легко сделать с двойной отправкой.

Эта гибкость достигается за счет: ассемблерный код будет использовать 2 косвенных вызова через виртуальную таблицу, а также динамическую трансляцию.

Информация о прямом типе информации не является самой большой конструкцией, как отметил Сергей: это всегда будет строгое сравнение типов, без специального случая.

Эта негибкость обеспечивается простотой в генерации кода: код должен только получить информацию о динамическом типе в начале vtable (и компилятор может легко оптимизировать эту выборку для объекта, где тип известен при компиляции время

ради любопытства, здесь some code generated:.. он TypeID оптимизирован прочь во время компиляции, в то время как двойной displatch все еще полагается на косвенные вызовы

0

Как указано в комментариях, следует еще одно возможное решение, не использует typeid и не полагается на dynamic_cast.

Я добавил несколько дополнительных примеров, чтобы показать, как вы можете легко определить семейство (например, здесь как D1, так и D1Bis отображаются как имеющие одинаковый тип семейства, даже если они фактически разные типы).
Не уверен, что это желаемая функция, так или иначе ...

Надеясь, что это вас заинтересовало.

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

Это в основном решение типа enum, которое было распространено в эпоху языка C. – ThomasMcLeod

+0

Ну, это по-прежнему используется сегодня, как правило, с помощью идиомы «CRTP», чтобы дать тип производным классам (например, 'entityx' делает что-то подобное), но он не подходит для иерархии с большим количеством чем на одном уровне, поэтому ... Я попытался адаптировать решение к вашей проблеме. – skypjack

+0

Не требуется для CRTP, поскольку вы можете использовать вывод типа шаблона, чтобы обнаружить параметр шаблона базового класса. – ThomasMcLeod