2012-12-24 1 views
4

Considérez ce qui suit:C++ 11 Cast dynamique If Else Chain -> Commutateur

struct B { }; 

template<typename T> 
struct D : B 
{ 
    T t; 
} 

void g(int i) { ... } 
void g(string s) { ... } 
void g(char c) { ... } 

void f(B* b) 
{ 
    if (dynamic_cast<D<int>*>(b)) 
    { 
     g(dynamic_cast<D<int>*>(b)->t); 
    } 
    else if (dynamic_cast<D<string>*>(b)) 
    { 
     g(dynamic_cast<D<string>*>(b)->t); 
    } 
    else if (dynamic_cast<D<char>*>(b)) 
    { 
     g(dynamic_cast<D<char>*>(c)->t) 
    } 
    else 
     throw error; 
}; 

Ici, il y a seulement trois types possibles de T - int, string, char - mais si la liste des types possibles étaient plus long, disons n, la chaîne if else prendrait O (n) pour s'exécuter.

Une façon de traiter cela serait de stocker un code de type supplémentaire dans D en quelque sorte, puis switch sur le code de type.

Le système RTTI doit déjà avoir un tel code. Y a-t-il un moyen d'y accéder et de l'allumer?

Ou y at-il une meilleure façon de faire ce que j'essaie de faire?

+0

@JoachimPileborg: Dans cet exemple de jouet, je pourrais simplement remplacer 'f' par' struct D {virtual void f() {g (t)}} ', mais cela ne correspond pas au plus gros problème. –

Répondre

3

C++ 11 est presque.

En C++ 03 c'était impossible parce que la seule façon d'obtenir une constante de temps de compilation (qui nécessite case) était à travers le système de type. Puisque typeid renvoie toujours le même type, il n'a pas pu produire différentes alternatives pour switch. C++ 11 ajoute constexpr et type_info::hash_code comme identifiant unique des types, mais ne les combine pas. Vous pouvez utiliser typeid dans une expression constante sur un nom de type ou des expressions statiques, mais étant donné que hash_code est une fonction non constexpr, vous ne pouvez pas l'appeler.

Bien sûr, il existe diverses solutions de contournement, dont une que vous décrivez, et les plus générales appliquent un visiteur sur un vecteur de type en utilisant la métaprogrammation de modèle.

+0

Donc je suppose que je pourrais utiliser 'unordered_map ' avec 'type_info :: hash_code' comme clé et un lambda qui fait le cast correspondant et appelle' g'. –

+0

@AndrewTomazosFathomlingCorps Cela fonctionnerait. Un foncteur modèle vous éviterait d'écrire plusieurs lambdas. Utilisez 'std :: size_t' au lieu de' int'. – Potatoswatter

+0

Downvoting parce que ce n'est pas une solution, et c'est très trompeur: "En C++ 03, c'était impossible" est juste des ordures pour "ça" = créer du code avec l'effet voulu. Pour ce qui est trivial à faire, comme indiqué dans ma réponse, la seule solution présentée ici jusqu'à présent. Qui actuellement, comme cela arrive souvent sur SO, a un compte de vote négatif. –

3

Depuis quelques types sont valides, vous pouvez résoudre cela avec des fonctions virtuelles et la spécialisation de modèle à la place:

struct B 
{ 
    virtual void g() = 0; 
} 

template<typename T> 
struct D : public B 
{ 
    T t; 
}; 

template<> 
struct D<int> : public B 
{ 
    int t; 
    void g() { /* do something here */ } 
}; 

template<> 
struct D<std::string> : public B 
{ 
    std::string t; 
    void g() { /* do something here */ } 
}; 

template<> 
struct D<char> : public B 
{ 
    char t; 
    void g() { /* do something here */ } 
}; 

void f(B* b) 
{ 
    b->g(); 
} 

Cela échouera à la compilation si vous fournissez les mauvais types, au lieu ou nécessitant des contrôles d'exécution (C++ est assez mauvais à).

+0

Le vrai problème a un grand nombre de types valides. –

+1

@AndrewTomazosFathomlingCorps Il peut toujours être une solution réalisable, en utilisant par ex. macros de préprocesseur pour aider avec les définitions de classe. –

-1

Le choix principal pour le type d'activation de l'exécution en C++ est une fonction virtuelle.

Il est mort simple:

#include <string> 
#include <iostream> 
using namespace std; 

struct Base 
{ 
    virtual void g() const = 0; 
}; 

template< class Type > void g(Type const&); 

template<> void g(int const&) { cout << "int" << endl; } 
template<> void g(string const&) { cout << "string" << endl; } 
template<> void g(char const&) { cout << "char" << endl; } 

template< class Type > 
struct Derived: Base 
{ 
    Type t; 
    virtual void g() const override { ::g<Type>(t); } 
}; 

void f(Base& b) { b.g(); } 

int main() 
{ 
    Derived<int>().g(); 
} 

Comme vous le pouvez est également efficace, O (1) au lieu de O stupide (n ). De plus, avec la vérification de type statique (compiler le temps) au lieu de la vérification de type dynamique (temps d'exécution), en économisant une quantité de tests plutôt ennuyante. Que puis-je dire de plus? Vraiment, oubliez le code de type et enums et autres. Rappelez-vous que Bertrand Meyer a choisi de ne supporte pas enums dans Eiffel, pour cette raison, que les gens ont tendance à en abuser pour les codes de type. Utilisez des fonctions virtuelles.

Hey, les fonctions virtuelles!

Ils sont vraiment utiles quand vous voudriez un envoi dynamique sur le type.

Donc, je recommande d'utiliser des fonctions virtuelles pour cela.:)


EDIT: afin ::g sans canevas pour éviter les ambiguïtés possibles dans le code réel.

+0

downvoter anonyme, vous avez échoué à informer les autres sur * quel * utilisateur il est si incompétent. oui, il informe les lecteurs de SO que le système de vote dans SO ne favorise pas la correction technique, et c'est bien. Votre vote n'a donc pas été totalement gaspillé, mais il aurait été encore mieux de savoir qui vous êtes. –