【问题标题】:Python's PubSub/observer Pattern for C++?用于 C++ 的 Python 的 PubSub/观察者模式?
【发布时间】:2010-10-10 23:25:16
【问题描述】:

我正在寻找 Python PubSub 库的 C++ 替代品,在该库中我不必将信号与插槽连接起来,而是可以注册一种特殊的消息,而无需知道哪些对象可以发送。

【问题讨论】:

  • 我认为您需要进一步充实这一点。这是一个进程间的需求还是只是一个进程中的对象?给我们一些关于您需要解决的问题的更多背景信息。您已经提到了观察者,这是解决此类问题的明显设计模式。
  • 我有一个 GUI 应用程序,数据存储在分层有序类中。当发生错误时,我想引发一个包含错误消息和代码的事件。现在我希望父数据类 GUI 对此事件作出反应,但数据类应该不知道 GUI,反之亦然。

标签: c++ python observer-pattern publish-subscribe


【解决方案1】:

你为什么不直接实现一个?这不是一个复杂的模式(好吧,取决于你真正想要的)。无论如何,我前段时间已经实现了一个快速而肮脏的。它没有经过优化、同步和单线程。我希望你可以用它来制作你自己的。

#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】:

    你可以使用 boost 库吗?如果是这样,那么结合函数和绑定库允许您执行以下操作。如果您的编译器支持,您可以使用 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.
    

    那么..这是您正在寻找的那种东西吗?

    请参阅herehere 了解大部分代码的来源。

    编辑:boost::signal 库也可以做你想做的事。

    【讨论】:

      【解决方案3】:

      也许你误解了什么是信号和槽。使用信号和插槽,您不必知道谁发送信号。您的“客户端”类只声明插槽,外部管理器可以将信号连接到它们。

      我建议您查看 Qt。这是一个了不起的跨平台库,不仅仅支持 GUI。它有一个方便高效的信号和槽实现,你可以使用。

      如今,它还获得了 LGPL 的许可(除了 GPL 和商业),因此您几乎可以将其用于任何目的。

      关于您的澄清评论,为什么不对错误提出异常?父母可以通知GUI,或者GUI可以注册父母发出的信号。这样父母也不必知道 GUI。

      【讨论】:

      • Qt 不是公司政策的选择原因。但是通过您的方法,我需要一位了解事件发送者和接收者的经理,但我并不总是拥有这些知识。另请参阅对最初问题的评论。
      猜你喜欢
      • 2021-11-22
      • 2013-12-30
      • 2014-05-26
      • 1970-01-01
      • 2012-08-19
      • 2016-02-20
      • 2023-04-10
      • 1970-01-01
      • 2019-01-26
      相关资源
      最近更新 更多