tags:

views:

1866

answers:

6

I've been trying to implement a C#-like event system in C++ with the tr1 function templates used to store a function that handles the event.

I created a vector so that multiple listeners can be attached to this event, i.e.:

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

I'd like to be able to remove a handler from the list to stop a listener receiving events.

So, how can I find the entry in this list that corresponds to a given listener? Can I test if a 'function' object in the list refers to a particular function?

Thanks!

EDIT: Having looked into the boost::signal approach, it seems it's probably implemented using a token system as some of you have suggested. Here's some info on this. An observer retains a "Connection" object when they attach to an event, and this connection object is used to disconnect if needed. So it looks like whether you use Boost or roll your own with tr1, the basic principle's the same. i.e. it will be a bit clumsy :)

+1  A: 

FAQ #1 in the boost function documentation seems to address your question - and the easy answer is "no".

Matt Cruikshank
+1  A: 

The proposal (section IIIb.) states they will not be comparable in any way. If you attach some extra information to them, you can easily identify each callback. For instance, if you simply define a struct wrapping the function pointer, you can remove them (assuming you have the same struct you inserted). You can also add some fields to the struct (like an automatically generated guid the client can hold on to) and compare against that.

hazzen
If you're going to use a struct with extra info, you may as well store the functors as hash values, and the GUID/whatever as hash keys. :-)
Chris Jester-Young
You can't store functors as hash values. You can't do anything with them. Wrapping them in a struct (even a one-element struct of just the functor) will allow you to do anything to them. It is intentional that they can't be compared/hashed/etc without some work.
hazzen
A: 

If you are storing function pointers only (and not other functors that match the signature required), this is easy (see code below). But in general, the answer, like other posters have said, is no. In that case, you probably want to store your functors in a hash, as values, with keys being something the user supplies on adding and removing.

The code below demonstrates how to get the functor/pointer object that is to be called. To use it, you must know the exact type of the object to extract (i.e., the typeid of the type you specify must match the typeid of the contained functor/pointer).

#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;
}
Chris Jester-Young
A: 

What about

map<key-type, function<void (int)> > listeners;
Flame
I think a hash (std::tr1::unordered_map) is a better choice for this sort of thing. Unless it's important to call listeners in the order of registration, in which case the key type should include/be a serial number.
Chris Jester-Young
+2  A: 

Okay, you got me working. The hard part is trying to match the exact usage pattern of C# events. If you skip that, there are MUCH easier ways to do what you're asking. (My co-worker Jason uses a Notifier object all over the place.) Anyway, here's the incredibly boring code which does what you want. Unfortunately, it doesn't allow you to pass parameters from the Subject to the Observer. To do that, you'd need to add even more smarts.

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

And here's the output:

doing event
done

doing event
..observed!
done

doing event
done

doing event
..observed!
..observed!
done
doing event
..observed!
done

Matt Cruikshank
+5  A: 

I don't know if you're locked into std C++ and tr1, but if you aren't, it seems like your problem could be completely avoided if you just used something like boost::signal and boost::bind to solve your original problem - creating an event system - instead of trying to roll your own.

Nick Bastin
Yes, it's important to see the forest despite the trees, so to speak. Thanks for writing the answer that I wish I had stepped back to think of!
Chris Jester-Young