Шаблон Python для PubSub / Observer для C ++? - PullRequest
1 голос
/ 03 марта 2009

Я ищу замену C ++ библиотеки Python PubSub, в которой мне не нужно связывать сигнал со слотом или около того, но вместо этого я могу зарегистрироваться для особого вида сообщений, не зная объекта, который может Отправь это.

Ответы [ 3 ]

2 голосов
/ 05 марта 2009

Можете ли вы использовать библиотеки повышения? Если это так, то объединение библиотек функций и связывания позволяет сделать следующее. Вы можете сделать то же самое, используя функциональность tr1, если ваш компилятор поддерживает это.

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

Итак, когда вы запустите это, вы получите:

play
Started playing
stop

Press any key to continue.

Итак ... это то, что вы ищете?

См. здесь и здесь для источника большей части этого кода.

РЕДАКТИРОВАТЬ: библиотека boost :: signal может также делать то, что вы хотите.

2 голосов
/ 06 марта 2009

Почему бы вам просто не внедрить один? Это не сложный шаблон (ну, в зависимости от того, что вы действительно хотите). Во всяком случае, я уже реализовал быстрый и грязный один некоторое время назад. Он не оптимизированный, синхронный и однопоточный. Я надеюсь, что вы можете использовать его, чтобы сделать свой собственный.

#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;
}
2 голосов
/ 03 марта 2009

Возможно, вы неправильно понимаете, что такое сигналы и слоты. С сигналами и слотами вам не нужно знать, кто посылает сигналы. Ваш "клиентский" класс просто объявляет слоты, и внешний менеджер может подключать к ним сигналы.

Я рекомендую вам проверить Qt. Это удивительная кроссплатформенная библиотека с гораздо большим, чем просто поддержка графического интерфейса. Он имеет удобную и эффективную реализацию сигналов и слотов, которые вы можете использовать.

В наши дни он также лицензирован с LGPL (в дополнение к GPL и коммерческой), поэтому вы можете использовать его практически для любых целей.

Повторяйте ваш уточняющий комментарий, почему бы не сделать исключение для ошибки? Родитель может уведомить GUI или, в качестве альтернативы, GUI может зарегистрироваться для сигнала, который излучает родитель. Таким образом, родитель также не должен знать о GUI.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...