5

我一直在尝试使用 tr1 函数模板在 C++ 中实现类似 C# 的事件系统,该函数模板用于存储处理事件的函数。

我创建了一个向量,以便可以将多个侦听器附加到此事件,即:

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

我希望能够从列表中删除一个处理程序以停止侦听器接收事件。

那么,如何在此列表中找到与给定侦听器对应的条目?我可以测试列表中的“函数”对象是否引用特定函数吗?

谢谢!

编辑:在研究了 boost::signal 方法后,似乎它可能是使用令牌系统实现的,正如你们中的一些人所建议的那样。这是有关此的一些信息。观察者在附加到事件时会保留一个“连接”对象,如果需要,此连接对象用于断开连接。所以看起来无论你是使用Boost还是使用tr1滚动你自己的,基本原理都是一样的。即它会有点笨拙:)

4

7 回答 7

8

我不知道您是否被锁定在标准 C++ 和 tr1 中,但如果没有,如果您只使用 boost::signal 和 boost::bind 之类的东西来解决您的问题,似乎可以完全避免您的问题原始问题 - 创建一个事件系统 - 而不是尝试自己推出。

于 2008-09-18T06:25:59.030 回答
4

好吧,你让我工作。困难的部分是尝试匹配 C# 事件的确切使用模式。如果你跳过它,有更简单的方法来做你所要求的。(我的同事 Jason 在所有地方都使用了一个 Notifier 对象。)不管怎样,这里有一些令人难以置信的无聊代码,它可以满足您的需求。不幸的是,它不允许您将参数从 Subject 传递给 Observer。为此,您需要添加更多智能。

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

这是输出:


做完 事件

做事件
..观察到!
完毕


做完 事件

做事件
..观察到!
..观察到的!
做完
事件
..观察到!
完毕

于 2008-09-18T04:46:25.480 回答
2

boost 函数文档中的常见问题解答 #1似乎解决了您的问题 - 简单的答案是“否”。

于 2008-09-18T02:42:03.033 回答
1

提案(第 IIIb. 节)指出,它们在任何方面都不可比较。如果您向它们附加一些额外的信息,您可以轻松识别每个回调。例如,如果您只是定义一个包装函数指针的结构,则可以删除它们(假设您插入的结构相同)。您还可以向结构中添加一些字段(例如客户端可以保留的自动生成的 guid)并与之进行比较。

于 2008-09-18T02:47:04.003 回答
0

我遇到了类似的问题并找到了解决方案。我使用了一些 C++0x 特性,但只是为了方便,它们不是必不可少的部分。看看这里:
>消息系统:回调可以是任何东西

于 2011-07-31T16:05:52.433 回答
0

如果您只存储函数指针(而不是与所需签名匹配的其他函子),这很容易(参见下面的代码)。但总的来说,就像其他海报所说的那样,答案是否定的。在这种情况下,您可能希望将仿函数作为值存储在哈希中,其中键是用户在添加和删除时提供的东西。

下面的代码演示了如何获取要调用的函子/指针对象。要使用它,您必须知道要提取的对象的确切类型(即,typeid您指定的类型必须与typeid包含的仿函数/指针的类型匹配)。

#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;
}
于 2008-09-18T02:53:42.723 回答
0

关于什么

map<key-type, function<void (int)> > listeners;
于 2008-09-18T03:16:24.383 回答