Overview
Features
Download
Documentation
Community
Add-Ons & Services
The POCO C++ Libraries Blog

Events and Delegates – or fun with templates

Filed under: Uncategorized by peter at 09:59

Were you ever in a situation where you wrote a piece of code, used it, loved it and half a year later you use it again and well, somehow your expectations have risen?
That happened to me with the events of POCO.
If you used them, you are probably famliar with that code:

Poco::BasicEvent<const std::string> ENameChanged;
Poco::BasicEvent<const std::pair<Address, Address> > EAddressChanged;
// first is old, 2nd is new

[...]

void PersonMonitor::add(Person& p)
{
    p.ENameChanged += Poco::Delegate<PersonMonitor, const std::string>(
        this, &PersonMonitor::onNameChanged);
    p.EAddressChanged += Poco::Expire<const std::pair<Address, Address> >(
        (Poco::Delegate<PersonMonitor, const std::pair<Address, Address> >(
            (this, &PersonMonitor::onAddressChanged), 1000);
}

Actually a lot of code just to add a delegate to an event. You need to know the type of the argument and the type of the receiver object. And then you have to wrap an expire object around the delegate only to tell it that the registration should expire in 1000 millisecs. For my taste that’s too much typing work. But it’s a template object, so you always have to specify the type.
But what if you’d use template functions?

template <class TObj, class TArgs>
static Delegate<TObj, TArgs> delegate(
    TObj* pObj,
    void (TObj::*NotifyMethod)(const void*, TArgs&))
{
    return Delegate<TObj, TArgs>(pObj, NotifyMethod);
}

template <class TObj, class TArgs>
static Expire<TArgs> delegate(
    TObj* pObj,
    void (TObj::*NotifyMethod)(const void*, TArgs&),
    Timestamp::TimeDiff expireMillisecs)
{
    return Expire<TArgs>(
        Delegate<TObj, TArgs>(pObj, NotifyMethod),
        expireMillisecs);
}

Now the compiler can determine the type of TObj and TArg automatically from the method parameters:

void PersonMonitor::add(Person& p)
{
    using Poco::delegate;
    p.ENameChanged    += delegate(this, &PersonMonitor::onNameChanged);
    p.EAddressChanged += delegate(this, &PersonMonitor::onAddressChanged, 1000);
}

Having fixed that, I took another look at the method signature of the callback methods.
Prior the method signature was fixed to:

void method(const void* pSender, TArg& ref)

I hardly ever use the sender parameter but other’s might.
So the goal was to make

  • the sender parameter optional, i.e. accept method signature with/without sender
  • also allow to add static methods (e.g.: C functions)
  • be fully backwards compatible

Suffice to say, I succeeded. I won’t post the code here because it would be too much text.

Watch out for POCO 1.3.1 which will contain these extensions :-)

6 Comments »
  1. Have you considered improving the way function arguments are defined in a delegate?

    For example:

    Delegate(pObj, NotifyMethod1); // no arguments
    Delegate(pObj, NotifyMethod2); // 1 argument
    Delegate(pObj, NotifyMethod3); // 2 arguments

    Any modern C++ compiler should be able to handle constructs like the ones above.

    I may give it a try myself…

    Thanks.

    Miguel

    Comment by Miguel on June 22, 2007, 05:38

  2. It appears that comments are filtered and anything that looks like HTML is removed. Let’t see if I can post my examples using HTML entities instead:

    Delegate<TObj>(pObj, NotifyMethod1); // no arguments
    Delegate<TObj, int>(pObj, NotifyMethod2); // 1 argument
    Delegate<TObj, int, char*>(pObj, NotifyMethod3); // 2 arguments

    Comment by Miguel on June 22, 2007, 05:42

  3. Hi Miguel,

    I think I understand your point. In the current implementation
    NotifyMethod is a typedef inside Delegate, so you need to use
    template specialization to provide for different method signatures (also required because the code to call NotifyMethod is different for each signature). You probably propose to define an external NotifyMethod and make NotifyMethod a parameter of Delegate?

    Also the code above will only compile if there is an abstract super-class for NotifyMethod from which all
    different method signatures extend from (don’t know, if I want that, would require one virtual method call before
    we could call the function pointer for each delegate).

    Anyway, a C++ compiler is not capable of auto resolving template parameters on objects or object constructor,
    but it can do so nicely on template functions.

    I’m not 100% happy with the current solution. My goal is
    to extend the whole events framework to work with an
    arbitrary number of parameters like

    Poco::BasicEvent < const Person, int, float >

    This also requires delegates to be expanded to cope
    with that. I am not sure how to do that. Template
    specializations in that case are way too much work.

    cu

    Peter

    Comment by peter on June 22, 2007, 07:51

  4. Hi Peter,

    I’ve written an alternative version of the Delegate class that is based on the excellent callback paper by Rich Hickey.

    You create a delegate with this syntax:

    Delegate<char*(int,float)> d1 = delegate(&obj, &ObjClass::method);
    Delegate<void(double)> d2 = delegate(&function);

    And then you can invoke them with:

    char* ret1 = d1(1, 2.0f); d2(3.4);

    My implementation doesn’t use any virtual functions and doesn’t allocate any memory, its performance is very close to calling a member function pointer directly.

    Unfortunately I don’t see how this stuff can be merged into your current Delegate implementation without introducing incompatiblities. A big difference is that my AbstractDelegate class does not have a notify() method since only the templated Delegate subclasses know the return and argument types.

    I still haven’t looked at your Event classes, but I suspect it will also be difficult to keep them compatible with existing code.

    I’ll be happy to send you the code once I’m finished with it if you are interested.

    Miguel

    Comment by Miguel on June 30, 2007, 17:23

  5. Does this really work? I’ve checked the source of 1.3.1 and 1.3.2 multiple times, tried the above examples, but I always end up stuck at the same problem this guy reported:
    http://pocoproject.org/poco/wiki/tiki-view_forum_thread.php?comments_parentId=783&topics_offset=9&forumId=6

    I of course did include “Poco/Delegate.h” and not even
    “using Poco::delegate;” does work (-> “‘delegate’ is not a member of ‘Poco’”) since it does seem to be in the source balls.

    Can anyone confirm this is really working and where in the source I can find it?

    thanks

    Comment by Horstl on March 17, 2008, 12:13

  6. If you want more power you could boost::function in combination with boost::bind.

    Francis

    Comment by Francis on April 17, 2008, 18:01

RSS RSS feed for comments on this post. TrackBack URI

Leave a comment