views:

639

answers:

3

Hi,

i'm looking for a C++ replacement of the Python PubSub Library in which i don't have to connect a signal with a slot or so, but instead can register for a special Kind of messages, without knowing the object which can send it.

+1  A: 

Perhaps you misunderstand what signals and slots are. With signals and slots you don't have to know who sends signals. Your "client" class just declares slots, and an outside manager can connect signals to them.

I recommend you to check out Qt. It's an amazing cross-platform library with much more than just GUI support. It has a convenient and efficient implementation of signals and slots which you can use.

These days it's also licensed with LGPL (in addition to GPL and commercial), so you can use it for practically any purpose.

Re your clarification comment, why not raise an exception for the error? The parent can notify the GUI, or alternatively the GUI can register for a signal the parent emits. This way the parent also doesn't have to know about the GUI.

Eli Bendersky
Qt is no option cause of company policies. But with your approach i would need a manager who knows the sender and the receiver of an event, but i don't have always this knowledge. See also comment on the initial question.
Andre
+1  A: 

Can you use the boost libraries? If so then combining the function and bind libraries allows you to do the following. You may be able to do the same using the tr1 functionality if your compiler supports it.

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

So when you run this you get:

play
Started playing
stop

Press any key to continue.

So.. is this the kind of thing you are looking for?

See here and here for the source of most of this code.

EDIT: The boost::signal library might also do what you want.

Jackson
+1 for recycling libraries that do what you want.
Nick Gerakines
+1  A: 

Why don't you just implement one? It's not a complicated pattern (well, depending what you really want). Anyway, I already implemented a quick and dirty one some time ago. It is not optimized, synchronous and single threaded. I hope you can use it to make your own.

#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;
}
Luppy