2009-12-01 1 views
3

Je tente de créer un système de messagerie dans lequel n'importe quelle classe dérivée de "Messageable" peut recevoir des messages basés sur la façon dont la fonction handleMessage() est surchargée. Par exemple:Comment puis-je créer une table de recherche basée sur le type afin de mettre en œuvre la répartition multiple en C++?

class Messageable { 
    public: 
     void takeMessage(Message& message) { 
      this->dispatchMessage(message); 
     } 
    protected: 
     void bindFunction(std::type_info type, /* Need help here */ func) { 
      m_handlers[type] = func; 
     } 

     void dispatchMessage(Message& message) { 
      m_handlers[typeid(message)](message); 
     } 
    private: 
     std::map<std::type_info, /*Need help here*/ > m_handlers; 
    }; 

class TestMessageable : public Messageable { 
    public: 
     TestMessageable() { 
      this->bindFunction(
       typeid(VisualMessage), 
       void (TestMessageable::*handleMessage)(VisualMessage)); 

      this->bindFunction(
       typeid(DanceMessage), 
       void (TestMessageable::*handleMessage)(DanceMessage)); 
     } 
    protected: 
     void handleMessage(VisualMessage visualMessage) { 
      //Do something here with visualMessage 
     } 

     void handleMessage(DanceMessage danceMessage) { 
      //Do something here with danceMessage 
     } 
}; 

En un mot, je veux la version correcte de handleMessage à appeler en fonction de la valeur RTTI d'un message donné.

Comment est-ce que je peux l'implémenter de préférence sans une sorte de déclaration de commutateur/cas monolithique.

+0

ne pas avoir ce genre Qt de chose? .... les événements & whatnot via la méthode virtuelle QObject :: Event() .... – ianmac45

Répondre

4

Vous devriez regarder dans le modèle Double Dispatch. Voir les informations here.

Vous devriez être en mesure de mettre en œuvre VisualMessage en tant que classe comme tel:

class VisualMessage : public Message 
{ 
    public: 
     virtual void dispatch(Messageable & inMessageable) 
     { 
      inMessageable.handleMessage(*this); 
     } 
}; 

puis l'appeler comme ceci:

Message & vMessage = VisualMessage(); 
Messageable & tMessageable = TestMessageable(); 
vMessage.dispatch(tMessageable); 

Il appellera ensuite TestMessageable :: handleMessage (VisualMessage & visualMessage

Cela est dû au fait que Message :: dispatch sera basé sur le type VisualMessage. Ensuite, lorsque VisualMessage :: dispatch appelle inMessageable.handleMessage (* this), il appellera le handleMessage droit, car le type * de ce pointeur est VisualMessage, pas Message.

+0

Si vous avez quelque chose comme Message * m = new VisualMessage(); puis appelez Messageable.handleMessage (* m); Messageable :: handleMessage (Message & m) sera-t-il appelé ou Messageable :: handleMessage (VisualMessage & vm) sera-t-il appelé? –

+0

Regardez le dernier exemple sur la page que j'ai référencée. Ce sera Messageable :: handleMessage (VisualMessage & vm) –

+0

Cette approche fonctionne presque, le seul problème que j'ai est qu'il est nécessaire de déclarer tous les messages possibles qui peuvent être reçus dans la classe de message de base. Comme dans le cas où je veux que VisualMessages soit récupérable, la classe Message doit avoir virtual void handleMessage (VisualMessage & vmessage); Y at-il un moyen pour la classe de base d'avoir seulement besoin de handleMessage (Message & message)? Ou est-ce impossible à éviter? –

-1

Vous trouverez ce genre de mise en œuvre dans Scott Meyers plus efficace C++ et article - 31 est ce que vous voulez & bien expliqué.

+0

Espérant obtenir ce livre (et l'original) pour Noël. Jusque-là, je vais devoir attendre pour vérifier cela. Merci quand même –

+0

si tu veux je peux t'envoyer cet article mec donne moi ton email id. – Ashish

+0

J'apprécie l'offre mais ça va, je vais attendre jusqu'à ce que je puisse obtenir le livre. –

1

Pour corriger votre code:

struct CompareTypeInfo 
    : std::binary_function<const std::type_info*, const std::type_info*, bool> 
{ 
    bool operator()(const std::type_info* a, const std::type_info* b) { 
     return a->before(*b); 
    } 
}; 

class Messageable 
{ 
protected: 
    typedef void (*handlefn)(Messageable *, Message &); 
    void bindFunction(const std::type_info& type, handlefn func) { 
     m_handlers[&type] = func; 
    } 

    void dispatchMessage(Message& message) { 
     m_handlers[&typeid(message)](this, message); 
    } 
    template <typename S, typename T> 
    static void handle(Messageable *self, Message &m) { 
     static_cast<S*>(self)->handleMessage(static_cast<T&>(m)); 
    } 
private: 
    std::map<const std::type_info*, handlefn, CompareTypeInfo> m_handlers; 
}; 

class TestMessageable : public Messageable 
{ 
public: 
    TestMessageable() 
     { 
     this->bindFunction(
      typeid(VisualMessage), &Messageable::handle<TestMessageable,VisualMessage>); 

     this->bindFunction(
      typeid(DanceMessage), &Messageable::handle<TestMessageable,DanceMessage>); 
     } 
public: 
    void handleMessage(VisualMessage visualMessage) 
     { 
     //Do something here with visualMessage 
     } 

    void handleMessage(DanceMessage danceMessage) 
     { 
     //Do something here with danceMessage 
     } 
    } 
}; 

Ces static_casts pourraient être dynamic_casts pour "sécurité supplémentaire" (en supposant qu'il ya des fonctions virtuelles) autour de coups de pieds. Mais la conception signifie que vous savez que self doit être un pointeur vers S, car sinon, cette fonction ne serait pas enregistrée, et vous savez que m doit faire référence à un T, car son ID de type a déjà été vérifié dans dispatchMessage. Donc, une distribution échouée ne peut pas arriver si la classe est utilisée correctement, et tout ce que vous pouvez faire si cela arrive est un débogage.

En fait, je pense que vous pouvez réduire le verbiage un peu plus en faisant bindFunction un modèle aussi:

template <typename S, typename T> 
void bindFunction(void) 
    { 
    m_handlers[&typeid(T)] = handle<S,T>; 
    } 

Ensuite, appelez avec:

this->bindFunction<TestMessageable,VisualMessage>(); 

Mais encore, vous pouvez voir pourquoi Steve Le code de double expédition de Rowe est généralement préféré ...

+0

Merci de résoudre le problème original, j'ai fini par utiliser une méthode Double Dispatch mais il est intéressant de voir comment le design original aurait pu être réalisé. –

+0

J'ai décidé d'essayer ce design afin que ma classe Messageable de base n'ait pas besoin de connaître tous les types de messages qu'elle peut recevoir. Certains des problèmes que j'ai rencontrés comprenaient std :: type_info n'étant pas compatible avec map (changé en utilisant std :: type_info.name()). La version raccourcie a bien fonctionné mais j'ai une question. Comment exatly ne " m_handlers [typeid (T)] = gérer ; " travail, n'a pas besoin de retourner quelque chose? Qu'est-ce qui me manque exactement? –

+0

handle dans la version de modèle de bindFunction est le même que handle et gérer dans la version longue: fonctions générées à partir de modèles, et nous utilisons des pointeurs. Ainsi, cette ligne affecte un pointeur à une fonction (fonction différente selon les paramètres du modèle) à un élément de la carte (élément différent selon T). Pour ce faire, tout ce que nous devons savoir, ce sont les types, donc nous n'avons pas besoin de paramètres à l'exécution. Si tout est en ligne, il en résultera à peu près le même code qu'avant. –

0

Ceci est une vieille question, mais la bibliothèque NUClear est conçue pour fournir un passage de message rapide et de type sécurisé dans une veine similaire à l'intention originale de cette question.

Full Disclosure: Je suis l'un des co-développeurs de NUCLÉAIRE

Dans ce cas, la classe TestMessageable est mis en œuvre en tant que NUClear::Reactor comme ceci:

#include <NUClear.h> 

// TestMessageable.h 
class TestMessageable : NUClear::Reactor { 
    public: 
     TestMessageable(NUClear::PowerPlant* powerPlant); 
    private: 
}; 

// TestMessageable.cpp 
#include "TestMessageable.h" 

TestMessageable::TestMessageable(NUClear::PowerPlant* powerPlant) 
: NUClear::Reactor(powerPlant) { 
    on<Trigger<VisualMessage>>([this](const VisualMessage& message) { 
     // Do something with VisualMessage here 
     // On can also take anything that is callable with a const& VisualMessage. 

     // Messages are sent using emit. 
     // If you don't have C++14 NUClear provides std::make_unique 
     auto classifiedData = std::make_unique<ClassifiedVision>(/* stuff */); 
     emit(std::move(classifieData)); 
    }); 

    on<Trigger<DanceMessage>>([this](const DanceMessage& message) { 
     // Do something with DanceMessage here. 
    }); 
} 
Questions connexes