Events and Delegates – or fun with templates
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







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
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
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
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
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
If you want more power you could boost::function in combination with boost::bind.
Francis
Comment by Francis on April 17, 2008, 18:01