Introduction
Back in 1998 - or even earlier - Microsoft introduced in its Visual J++ language the delegate as a flexible alternative to Sun's Java subscriber pattern for handling events generated by visual elements like buttons and text boxes. In fact, the delegate has been a matter of controversy between Microsoft and Sun as the latter never accepted it to be part of the Java Standard. For a verbose yet informative description of delegates and Java subscriber pattern, please refer to Chris Sells' article .NET Delegates: A C# Bedtime Story.
In this article, we present an implementation of the delegate pattern using C++ templates. In the accompanying demonstration project, we provide a simplistic emulation of an event source object - that we call Button
- firing a Click
event, together with an object we call ButtonContainer
that defines a click event handler and registers it with the Button
event source.
The Delegate Pattern
The .NET delegate pattern is comprised of three things:
- An event handling function which can be an instance or
static
class method. - An event-handler wrapper, designated in C# with the
delegate
keyword. - The event, this is a collection of
delegate
objects with a +=
operator overload plus certain access restrictions so that a client cannot fire the contained events explicitly.
In a typical operating scenario, a class EH
wishing to handle the event X
generated by class ES
, defines a handler with a signature appropriate for the event it wishes to handle. In Windows-Forms C# code, this is declared as follows:
class EH {
ES es_;
...
void EventX_Handler(Object sender, EventArgs e) { ... }
...
}
Then the code registers a delegate
object of type EventHandler
, with the X
event of EH.es_
:
class EH {
ES es_;
...
void EventX_Handler(Object sender, EventArgs e) { ... }
...
public EH() {
...
es_.eventX += new EventHandler(EventX_Handler);
...
}
}
The C++ Template Implementation and Demonstration Project
First, we create a base template class for delegates with a two-argument signature: the sender object and some custom type that designates the event arguments:
template<typename _Arg1, typename _Arg2> struct ClosureBase {
virtual void operator ()(_Arg1 &arg1, _Arg2 &arg2) = 0;
};
In this, and in the following code, we prefer the word closure instead of delegate because the latter is reserved in C++.NET 2005. As you will see, it is an abstract
base class for all delegates with two arguments.
The Button
event source class can then be completely defined using this template only:
class Button {
public:
struct ClickEventArguments {
int xCoord_, yCoord_;
ClickEventArguments(int xCoord = 0, int yCoord = 0) :
xCoord_(xCoord), yCoord_(yCoord){ }
};
typedef vector<ClosureBase<Button, ClickEventArguments> *> ClickEvents;
ClickEvents clickEvents_;
void FireClickEvents(int xc, int yc) {
ClickEventArguments args(xc, yc);
for(ClickEvents::iterator it = clickEvents_.begin();
it != clickEvents_.end(); it++) {
(*(*it))(*this, args);
}
}
};
Only code relevant to the current discussion is presented here. As you can see, the Click
event of Button
class is a vector of ClosureBase
pointers. The FireClickEvents
method iterates through the event elements calling their ()
operator.
The ButtonContainer
class contains a Button
object and registers a Click
event-handler:
class ButtonContainer {
Button button_;
void ButtonClickHandler(Button &sender, Button::ClickEventArguments &arguments)
{
cout <<
"Delegate invoked with sender " <<
typeid(sender).name() << ", and eventArgs (" <<
arguments.xCoord_ << ", " << arguments.yCoord_ << ")" << endl;
}
public:
ButtonContainer()
{
button_.clickEvents_.push_back(CreateClosure(this,
&ButtonContainer::ButtonClickHandler));
}
};
The only "weird" thing is the CreateClosure
in the ButtonContainer
constructor. As one can imagine, CreateClosure
is a generator for a ClosureBase
derived template that is parameterized on the event arguments and the event-handling object:
template<class _Ty, typename _Arg1, typename _Arg2>
class Closure : public ClosureBase<_Arg1, _Arg2> {
void (_Ty::* method_)(_Arg1 &, _Arg2 &);
_Ty *objectPtr_;
public:
Closure(_Ty *objectPtr,
void (_Ty::* method)(_Arg1 &, _Arg2 &)) : objectPtr_(objectPtr)
{ method_ = method; }
virtual ~Closure() { }
virtual void operator ()(_Arg1 &arg1, _Arg2 &arg2)
{ return (objectPtr_->*method_)(arg1, arg2); }
};
Closure
template associates an object pointer with the event-handling method signature through the attributes method_
and objectPtr_
. The first is a pointer to a class method and the second an object pointer. Then the ()
operator calls the ->*method_ of objectPtr_
object pointer:
(objectPtr_->*method_)(arg1, arg2)
For related implementations, please see the code in the functional STL header.
Last, the creation of Closure
objects is done through CreateClosure
generator function, to make the code more readable at client site:
template<class _Ty, typename _Arg1, typename _Arg2>
inline Closure<_Ty, _Arg1, _Arg2>
*CreateClosure(_Ty *object, void(_Ty::*method)(_Arg1 &, _Arg2 &)){
return new Closure<_Ty, _Arg1, _Arg2>(object, method);
}
In the demo project, we have also included a WindowManager
class that is supposed to emulate the operating system.
In a more complete demonstration, WindowManager
should run in a background thread. The WindowManager
is of course not relevant to our subject but it implements a very nice singleton (known as Meyers singleton).
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.