2008-09-18 33 views
5

J'ai essayé d'implémenter un système d'événements de type C# en C++ avec les modèles de fonctions tr1 utilisés pour stocker une fonction qui gère l'événement.Comparaison des objets std :: tr1 :: function <>

J'ai créé un vecteur pour que les auditeurs peuvent être attachés multiples à cet événement, i.e. .:

vector< function<void (int)> > listenerList; 

Je voudrais pouvoir supprimer un gestionnaire de la liste pour arrêter un auditeur de recevoir des événements. Alors, comment puis-je trouver l'entrée dans cette liste qui correspond à un écouteur donné? Puis-je tester si un objet 'function' dans la liste fait référence à une fonction particulière?

Merci! EDIT: Après avoir examiné l'approche boost :: signal, il semble qu'il soit probablement implémenté en utilisant un système de jeton comme certains d'entre vous l'ont suggéré. Here's some info on this. Un observateur conserve un objet "Connexion" lorsqu'il se connecte à un événement, et cet objet de connexion est utilisé pour se déconnecter si nécessaire. Donc, il semble que vous utilisiez Boost ou rouliez le vôtre avec tr1, le principe de base est le même. c'est-à-dire que ce sera un peu maladroit :)

Répondre

2

FAQ #1 Dans la documentation de la fonction boost, vous semblez répondre à votre question - et la réponse facile est "non".

1

Le proposal (section IIIb) indique qu'ils ne seront en aucune façon comparables. Si vous leur ajoutez des informations supplémentaires, vous pouvez facilement identifier chaque rappel. Par exemple, si vous définissez simplement un struct entourant le pointeur de fonction, vous pouvez les supprimer (en supposant que vous ayez la même structure que vous avez insérée). Vous pouvez également ajouter des champs à la structure (comme un guid généré automatiquement que le client peut conserver) et comparer avec cela.

+0

Si vous allez utiliser une structure avec des informations supplémentaires, vous pouvez aussi stocker les foncteurs comme des valeurs de hachage, et le GUID/any comme des clés de hachage. :-) –

+0

Vous ne pouvez pas stocker les foncteurs comme valeurs de hachage. Tu ne peux rien faire avec eux. Les emballer dans une structure (même une structure d'un seul élément du foncteur) vous permettra de faire n'importe quoi. Il est intentionnel qu'ils ne puissent pas être comparés/hachés/etc sans travail. – hazzen

0

Si vous stockez uniquement des pointeurs de fonction (et pas d'autres foncteurs correspondant à la signature requise), c'est facile (voir le code ci-dessous). Mais en général, la réponse, comme d'autres affiches ont dit, est non. Dans ce cas, vous voulez probablement stocker vos foncteurs dans un hachage, en tant que valeurs, avec des clés que l'utilisateur fournit lors de l'ajout et de la suppression.

Le code ci-dessous montre comment obtenir l'objet foncteur/pointeur à appeler. Pour l'utiliser, vous devez connaître le type exact de l'objet à extraire (c'est-à-dire que le typeid du type que vous spécifiez doit correspondre au typeid du foncteur/pointeur contenu).

#include <cstdio> 
#include <functional> 

using std::printf; 
using std::tr1::function; 

int main(int, char**); 
static function<int (int, char**)> main_func(&main); 

int 
main(int argc, char** argv) 
{ 
    printf("%p == %p\n", *main_func.target<int (*)(int, char**)>(), &main); 
    return 0; 
} 
0

Qu'en est-

map<key-type, function<void (int)> > listeners; 
+0

Je pense qu'un hash (std :: tr1 :: unordered_map) est un meilleur choix pour ce genre de chose. Sauf s'il est important d'appeler les auditeurs dans l'ordre d'enregistrement, auquel cas le type de clé doit inclure/être un numéro de série. –

4

D'accord, tu me as travail. La partie difficile est d'essayer de faire correspondre le modèle d'utilisation exacte des événements C#. Si vous passez cette étape, il y a BEAUCOUP plus facile de faire ce que vous demandez. (Mon collègue Jason utilise un objet Notifier partout.) De toute façon, voici le code incroyablement ennuyeux qui fait ce que vous voulez. Malheureusement, cela ne vous permet pas de transmettre des paramètres du sujet à l'observateur. Pour ce faire, vous devrez ajouter encore plus d'intelligence.

#include "stdafx.h" 
#include <iostream> 
#include <string> 
#include <list> 
#include <algorithm> 
#include <boost/tr1/functional.hpp> 
#include <boost/tr1/memory.hpp> 

using namespace std; 
using namespace std::tr1; 

template <typename T> 
class ObserverHandle 
{ 
public: 
    typedef boost::function<void (T*)> const UnderlyingFunction; 

    ObserverHandle(UnderlyingFunction underlying) 
     : _underlying(new UnderlyingFunction(underlying)) 
    { 
    } 

    void operator()(T* data) const 
    { 
     (*_underlying)(data); 
    } 

    bool operator==(ObserverHandle<T> const& other) const 
    { 
     return (other._underlying == _underlying); 
    } 

private: 
    shared_ptr<UnderlyingFunction> const _underlying; 
}; 

class BaseDelegate 
{ 
public: 
    virtual bool operator==(BaseDelegate const& other) 
    { 
     return false; 
    } 

    virtual void operator()() const = 0; 
}; 

template <typename T> 
class Delegate : public BaseDelegate 
{ 
public: 
    Delegate(T* observer, ObserverHandle<T> handle) 
     : _observer(observer), 
     _handle(handle) 
    { 
    } 

    virtual bool operator==(BaseDelegate const& other) 
    { 
     BaseDelegate const * otherPtr = &other; 
     Delegate<T> const * otherDT = dynamic_cast<Delegate<T> const *>(otherPtr); 
     return ((otherDT) && 
      (otherDT->_observer == _observer) && 
      (otherDT->_handle == _handle)); 
    } 

    virtual void operator()() const 
    { 
     _handle(_observer); 
    } 

private: 
    T* _observer; 
    ObserverHandle<T> _handle; 
}; 

class Event 
{ 
public: 
    template <typename T> 
    void add(T* observer, ObserverHandle<T> handle) 
    { 
     _observers.push_back(shared_ptr<BaseDelegate>(new Delegate<T>(observer, handle))); 
    } 

    template <typename T> 
    void remove(T* observer, ObserverHandle<T> handle) 
    { 
     // I should be able to come up with a bind2nd(equals(dereference(_1))) kind of thing, but I can't figure it out now 
     Observers::iterator it = find_if(_observers.begin(), _observers.end(), Compare(Delegate<T>(observer, handle))); 
     if (it != _observers.end()) 
     { 
      _observers.erase(it); 
     } 
    } 

    void operator()() const 
    { 
     for (Observers::const_iterator it = _observers.begin(); 
      it != _observers.end(); 
      ++it) 
     { 
      (*(*it))(); 
     } 
    } 

private: 
    typedef list<shared_ptr<BaseDelegate>> Observers; 
    Observers _observers; 

    class Compare 
    { 
    public: 
     Compare(BaseDelegate const& other) 
      : _other(other) 
     { 
     } 

     bool operator() (shared_ptr<BaseDelegate> const& other) const 
     { 
      return (*other) == _other; 
     } 

    private: 
     BaseDelegate const& _other; 
    }; 
}; 

// Example usage: 

class SubjectA 
{ 
public: 
    Event event; 

    void do_event() 
    { 
     cout << "doing event" << endl; 
     event(); 
     cout << "done" << endl; 
    } 
}; 

class ObserverA 
{ 
public: 
    void test(SubjectA& subject) 
    { 
     subject.do_event(); 
     cout << endl; 

     subject.event.add(this, _observe); 
     subject.do_event(); 
     subject.event.remove(this, _observe); 
     cout << endl; 

     subject.do_event(); 
     cout << endl; 

     subject.event.add(this, _observe); 
     subject.event.add(this, _observe); 
     subject.do_event(); 
     subject.event.remove(this, _observe); 
     subject.do_event(); 
     subject.event.remove(this, _observe); 
     cout << endl; 

    } 

    void observe() 
    { 
     cout << "..observed!" << endl; 
    } 

private: 
    static ObserverHandle<ObserverA> _observe; 
}; 

// Here's the trick: make a static object for each method you might want to turn into a Delegate 
ObserverHandle<ObserverA> ObserverA::_observe(boost::bind(&ObserverA::observe, _1)); 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    SubjectA sa; 
    ObserverA oa; 
    oa.test(sa); 

    return 0; 
} 

Et voici la sortie:

événement faisant
fait

événement faisant
..observed!
fait

événement faisant
fait

événement faisant
..observed!
..observed!
fait
faisant l'événement
..observed!
fait

8

Je ne sais pas si vous êtes enfermé dans std C++ et TR1, mais si vous n'êtes pas, il semble que votre problème pourrait être complètement évité si vous venez d'utiliser quelque chose comme boost :: Signal et boost :: bind pour résoudre votre problème d'origine - créer un système d'événements - au lieu d'essayer de rouler le vôtre.

+1

Oui, il est important de voir la forêt malgré les arbres, pour ainsi dire. Merci d'avoir écrit la réponse que j'aurais aimé voir! –

0

J'ai rencontré un problème similaire et trouvé une solution. J'ai utilisé certaines fonctionnalités C++ 0x, mais seulement pour plus de commodité, elles ne sont pas une partie essentielle. Jetez un coup d'oeil ici:
>Messaging system: Callbacks can be anything