2009-03-03 9 views
1

ich suche nach einem C++ Ersatz der Python PubSub Library, in dem ich kein Signal mit einem Slot oder so verbinden muss, sondern stattdessen für eine spezielle Art von Nachrichten registrieren kann, ohne das Objekt zu kennen, das senden kann es.Python PubSub/Beobachter Muster für C++?

+0

Ich denke, Sie müssen das etwas mehr ausstatten. Ist dies eine prozessübergreifende Anforderung oder nur Objekte innerhalb eines Prozesses? Geben Sie uns mehr Hintergrundinformationen zu dem Problem, das Sie lösen müssen. Sie erwähnen bereits Observer, was das offensichtliche Entwurfsmuster für diese Art von Problem ist. – Jackson

+0

Ich habe eine GUI-App, die Daten werden in hierarchisch geordneten Klassen gespeichert. Wenn ein Fehler auftritt, möchte ich ein Ereignis auslösen, das die Fehlermeldung und den Code enthält. Nun möchte ich die Eltern-Datenklasse _und_ die GUI reagieren auf dieses Ereignis, aber die Datenklasse sollte keine Kenntnis der GUI haben und umgekehrt. – Andre

Antwort

2

Vielleicht verstehen Sie falsch, welche Signale und Slots sind. Mit Signalen und Slots müssen Sie nicht wissen, wer Signale sendet. Ihre "Client" -Klasse deklariert nur Slots, und ein externer Manager kann Signale mit ihnen verbinden.

Ich empfehle Ihnen, sich Qt. Es ist eine erstaunliche plattformübergreifende Bibliothek mit viel mehr als nur GUI-Unterstützung. Es hat eine praktische und effiziente Implementierung von Signalen und Slots, die Sie verwenden können.

Heute ist es auch lizenziert mit LGPL (zusätzlich zu GPL und kommerziellen), so dass Sie es für praktisch jeden Zweck verwenden können.

Re Ihrer Erläuterung, warum nicht eine Ausnahme für den Fehler auslösen? Der Elternteil kann die GUI benachrichtigen, oder alternativ kann sich die GUI für ein Signal registrieren, das der Elternteil aussendet. Auf diese Weise muss der Elternteil auch nichts über die GUI wissen.

+0

Qt ist keine mögliche Ursache für Unternehmensrichtlinien. Aber mit Ihrem Ansatz würde ich einen Manager brauchen, der den Absender und den Empfänger eines Ereignisses kennt, aber ich habe nicht immer dieses Wissen. Siehe auch die erste Frage kommentieren. – Andre

2

Können Sie die Boost-Bibliotheken verwenden? Wenn dies der Fall ist, können Sie mit der Kombination der Bibliotheken function und bind Folgendes tun. Möglicherweise können Sie dasselbe mit der tr1-Funktionalität tun, wenn Ihr Compiler dies unterstützt.

#include <iostream> 
#include <list> 
#include <boost/function.hpp> 
#include <boost/bind.hpp> 

typedef boost::function< void() > EVENT_T ; 

template<typename F> 
class Subject 
{ 
    public: 
     virtual void attach (F o) 
     { 
      obs_.push_back (o); 
     } 

     virtual void notify() 
     { 
      for (typename std::list<F>::iterator i = obs_.begin(); i != obs_.end(); ++i) 
       (*i)(); 
     } 

    private: 
     std::list<F> obs_; 
} ; 

class Button : public Subject<EVENT_T> 
{ 
    public: 
     void onClick() 
     { 
      notify() ; 
     }; 
}; 

class Player 
{ 
    public: 

     void play() 
     { 
      std::cout << "play" << std::endl ; 
     } 
     void stop() 
     { 
      std::cout << "stop" << std::endl ; 
     } 

}; 

class Display 
{ 
    public: 
     void started() 
     { 
      std::cout << "Started playing" << std::endl ; 
     } 
}; 

Button playButton ; 
Button stopButton ; 
Player thePlayer; 
Display theDisplay ; 

int main (int argc, char **argv) 
{ 
    playButton.attach (boost::bind (&Player::play, &thePlayer)); 
    playButton.attach (boost::bind (&Display::started, &theDisplay)); 
    stopButton.attach (boost::bind (&Player::stop, &thePlayer)); 

    playButton.onClick() ; 
    stopButton.onClick() ; 
    return 0; 
} 

Also, wenn Sie diese laufen Sie erhalten:

play 
Started playing 
stop 

Press any key to continue. 

Also .. das ist die Art von Dingen, die Sie suchen?

Die Quelle des Großteils dieses Codes finden Sie unter here und here.

EDIT: Die boost :: signal-Bibliothek könnte auch tun, was Sie wollen.

+0

+1 für das Recycling von Bibliotheken, die tun, was Sie wollen. –

2

Warum implementieren Sie nicht einfach einen? Es ist kein kompliziertes Muster (naja, abhängig davon, was du wirklich willst). Wie auch immer, ich habe schon vor einiger Zeit eine schnelle und dreckige implementiert. Es ist nicht optimiert, synchron und single threaded. Ich hoffe, du kannst es benutzen, um es selbst zu machen.

#include <vector> 
#include <iostream> 
#include <algorithm> 

template<typename MESSAGE> class Topic; 
class Subscriber; 

class TopicBase 
{ 
    friend class Subscriber; 
private: 
    virtual void RemoveSubscriber(Subscriber* subscriber)=0; 
}; 

template<typename MESSAGE> 
class Topic : public TopicBase 
{ 
    friend class Subscriber; 
private: 
    class Callable 
    { 
    public: 
     Callable(Subscriber* subscriber, void (Subscriber::*method)(const MESSAGE&)) 
      :m_subscriber(subscriber) 
      ,m_method(method) 
     { 
     } 
     void operator()(const MESSAGE& message) 
     { 
      (m_subscriber->*m_method)(message); 
     } 
     bool operator==(const Callable& other) const 
     { 
      return m_subscriber == other.m_subscriber && m_method == other.m_method; 
     } 
    public: 
     Subscriber* m_subscriber; 
     void (Subscriber::*m_method)(const MESSAGE&); 
    }; 
public: 
    ~Topic() 
    { 
     //unregister each subscriber 
     for(std::vector<Callable>::iterator i = m_subscribers.begin(); i != m_subscribers.end(); i++) 
     { 
      std::vector<TopicBase*>& topics = i->m_subscriber->m_topics; 
      for(std::vector<TopicBase*>::iterator ti = topics.begin();;) 
      { 
       ti = std::find(ti, topics.end(), this); 
       if(ti == topics.end()) break; 
       ti = topics.erase(ti); 
      } 
     } 
    } 
    void SendMessage(const MESSAGE& message) 
    { 
     for(std::vector<Callable>::iterator i = m_subscribers.begin(); i != m_subscribers.end(); i++) 
     { 
      (*i)(message); 
     } 
    } 
private: 
    void Subscribe(Subscriber* subscriber, void (Subscriber::*method)(const MESSAGE&)) 
    { 
     m_subscribers.push_back(Callable(subscriber, method)); 
     subscriber->m_topics.push_back(this); 
    } 
    void Unsubscribe(Subscriber* subscriber, void (Subscriber::*method)(const MESSAGE&)) 
    { 
     std::vector<Callable>::iterator i = std::find(m_subscribers.begin(), m_subscribers.end(), Callable(subscriber, method)); 
     if(i != m_subscribers.end()) 
     { 
      m_subscribers.erase(i); 
      subscriber->m_topics.erase(std::find(subscriber->m_topics.begin(), subscriber->m_topics.end(), this)); //should always find one 
     } 
    } 
    virtual void RemoveSubscriber(Subscriber* subscriber) 
    { 
     for(std::vector<Callable>::iterator i = m_subscribers.begin() ; i != m_subscribers.end(); i++) 
     { 
      if(i->m_subscriber == subscriber) 
      { 
       m_subscribers.erase(i); 
       break; 
      } 
     } 
    } 
private: 
    std::vector<Callable> m_subscribers; 
}; 


class Subscriber 
{ 
    template<typename T> friend class Topic; 
public: 
    ~Subscriber() 
    { 
     for(std::vector<TopicBase*>::iterator i = m_topics.begin(); i !=m_topics.end(); i++) 
     { 
      (*i)->RemoveSubscriber(this); 
     } 
    } 
protected: 
    template<typename MESSAGE, typename SUBSCRIBER> 
    void Subscribe(Topic<MESSAGE>& topic, void (SUBSCRIBER::*method)(const MESSAGE&)) 
    { 
     topic.Subscribe(this, static_cast<void (Subscriber::*)(const MESSAGE&)>(method)); 
    } 
    template<typename MESSAGE, typename SUBSCRIBER> 
    void Unsubscribe(Topic<MESSAGE>& topic, void (SUBSCRIBER::*method)(const MESSAGE&)) 
    { 
     topic.Unsubscribe(this, static_cast<void (Subscriber::*)(const MESSAGE&)>(method)); 
    } 
private: 
    std::vector<TopicBase*> m_topics; 
}; 

// Test 

Topic<int> Topic1; 

class TestSubscriber1 : public Subscriber 
{ 
public: 
    TestSubscriber1() 
    { 
     Subscribe(Topic1, &TestSubscriber1::onTopic1); 
    } 
private: 
    void onTopic1(const int& message) 
    { 
     std::cout<<"TestSubscriber1::onTopic1 "<<message<<std::endl; 
    } 
}; 

class TestSubscriber2 : public Subscriber 
{ 
public: 
    void Subscribe(Topic<const char*> &subscriber) 
    { 
     Subscriber::Subscribe(subscriber, &TestSubscriber2::onTopic); 
    } 
    void Unsubscribe(Topic<const char*> &subscriber) 
    { 
     Subscriber::Unsubscribe(subscriber, &TestSubscriber2::onTopic); 
    } 
private: 
    void onTopic(const char* const& message) 
    { 
     std::cout<<"TestSubscriber1::onTopic1 "<<message<<std::endl; 
    } 
}; 


int main() 
{ 
    Topic<const char*>* topic2 = new Topic<const char*>(); 
    { 
     TestSubscriber1 testSubscriber1; 
     Topic1.SendMessage(42); 
     Topic1.SendMessage(5); 
    } 
    Topic1.SendMessage(256); 

    TestSubscriber2 testSubscriber2; 
    testSubscriber2.Subscribe(*topic2); 
    topic2->SendMessage("owl"); 
    testSubscriber2.Unsubscribe(*topic2); 
    topic2->SendMessage("owl"); 
    testSubscriber2.Subscribe(*topic2); 
    delete topic2; 

    return 0; 
}