Introduction
C# has a language feature called 'delegates' which makes it easy to detach the originator of an event from the ultimate handler. They perform essentially the same role as function pointers in C and pointers to member functions in C++, but they are much more flexible. In particular they can be used to point to any function on any object, as long as it has the appropriate signature.
This article explains my approach for providing the same functionality using only standard C++. There are many worthy alternatives which you can find easily by Googling with "C++ delegates". The focus of my effort was to obtain a syntax very similar to that in Managed C++ and C#.
Background
If you already know everything you want to know about delegates, please skip this section.
Delegates are not a new idea. Borland's Delphi and C++ Builder products have used them from the outset to support the Visual Component Library, though they are called 'method pointers' in Delphi, and 'closures' in Builder (they are the same thing as far as I know). Basically a closure is an OO function pointer. Internally it simply holds the address of the function to be called plus the address of the object on which it is being called (i.e. the hidden 'this' parameter which is passed to the function).
The important point is this: being agnostic about which other class is going to handle the events a class generates is a key factor in making Delphi's wonderful component-based visual development framework possible. Reduced coupling between classes is a Good Thing.
Now C#-style delegates provide the same service to .NET languages but, sadly, Standard C++ does not have them. Pointers to member functions are very restricted in comparison, though they have been used in conjunction with macros in libraries such as Borland's OWL in the past, and (I think) with Trolltech's Qt today. And that's why I'm writing this article.
With the .NET delegates, you can even attach several handlers to a single event. They will all be called (sequentially) when the delegate is invoked. This means you can safely attach your handler to an event without breaking someone else's connection. I haven't used this feature yet, but I recognize the potential. However, I do question whether such a feature can be implemented efficiently. Invoking Borland's single-cast closures boils down to a couple of opcodes which push 'this' and call the function, so they are powerful but still cheap to use. [It's a pity they're not in the Standard...] But once you start maintaining a dynamic collection of targets, life gets more complicated. Ideally, .NET should have really efficient single-cast delegates, and implement multicast delegates in terms of those - best of both worlds. For all I know, that's what it does.
.NET distinguishes between 'delegates' and 'events'. A delegate is a glorified function pointer, as I have said; an event is an application of a delegate - a member of a class to which you can assign the addresses of handlers which will be called when the class invokes the delegate. I admit to finding the distinction unhelpful - how else would delegates be used, anyway? I sometimes use the terms interchangeably.
C# and Managed C++ have a rather tidy syntax for assigning handlers to a delegate/event:
mnuPopup->Popup += new System::EventHandler(this, mnuPopup_Popup);
When the context menu, mnuPopup
, is displayed by the user, it will invoke its Popup event/delegate. This will then call the handler I have implemented in my form, mnuPopup_Popup
. I have tried to preserve something like this syntax in my code.
Understanding the code
Enough waffling!! Let's talk about the code. If you couldn't care less about how it works, just skip this section and head straight for 'Using the code'
The design is intended to satisfy the following constraints:
- The delegate can call non-static member functions on an object of any class, including virtual functions.
- The delegate can call static member functions of any class, and free functions.
- The delegate is multicast.
- The delegate is easy to use (i.e. keeps close to the C# syntax).
There are four parts to my solution:
- A base class to specify signature without reference to any actual classes. It has a pure virtual overload of operator(). This is a private nested class.
- An outer class actually used by clients. It invokes
operator()
on a collection of derivatives of the base class.
- Derived classes which implement the overloaded
operator()
declared in the base class. This is a public nested template class, and allows us to specify the type of an event target.
- A derived class to handle static and free functions. This is a public nested class.
The base class looks like this (Return and Arg1 are types from the outer class):
class Base
{
public:
virtual ~Base() { }
virtual Return operator()(Arg1) = 0;
};
The derived class for non-static functions looks like this (Return and Arg1 are types from the outer class):
template <typename Class>
class T : public Base
{
typedef Return (Class::*Func)(Arg1);
private:
Class* mThis;
Func mFunc;
public:
T(Class* aThis, Func aFunc) : mThis(aThis), mFunc(aFunc) { }
virtual Return operator()(Arg1 arg1)
{
return (mThis->*mFunc)(arg1);
}
};
The derived class for static and free functions looks like this (Return and Arg1 are types from the outer class):
class S : public Base
{
typedef Return (*Func)(Arg1);
private:
Func mFunc;
public:
S(Func aFunc) : mFunc(aFunc) { }
virtual Return operator()(Arg1 arg1)
{
return mFunc(arg1);
}
};
The outer class looks like this (with many details missing):
template <typename Return, typename Arg1>
class Event
{
private:
std::vector<Base*> mPtrs;
class Base { ... };
public:
template <typename Class>
class T : public Base { ... };
class S : public Base { ... };
Event& operator+=(Base* aPtr)
{
mPtrs.push_back(aPtr);
return *this;
}
Return operator()(Arg1 arg1)
{
typename std::vector<Base*>::iterator end = mPtrs.end();
for (typename std::vector<Base*>::iterator i = mPtrs.begin();
i != end; ++i)
{
if ((i + 1) == end)
return (*(*i))(arg1);
else
(*(*i))(arg1);
}
}
};
There is still some work to do. I want to make it safe to copy these objects, and I need to come up with something for multicast delegates with a signature that returns are value. Perhaps I could return a vector of results. I also need to duplicate the template to cope with two or more arguments to the signature.
No doubt this implementation is very slow compared to function pointers but the usual application of events is GUI work, so speed is not so critical. I'd be very interested to see what the underlying implementation of .NET delegates and events looks like...
It seems that my Event class would be most useful as a public member of any class wishing to expose an event. This breaks encapsulation, but I think the syntax for adding targets would get pretty hairy otherwise. We want to prevent clients of a class using Event from invoking operator()
on it. Perhaps a simple adapter would do the job - it would be a public member passing calls on to a private Event member, but not exposing operator()
.
Using the code
To try this out, download the demo code. Everything is contained in a single file to make life easy. Just compile it and run it from the command prompt. This code was developed on g++ 3.2. I'm keen to know which other compilers like it, and which ones don't, so please let me know.
struct TShapes
{
virtual void Square(int i)
{ std::cout << "TShapes::Square: " << i << std::endl; }
void Triangle(int i)
{ std::cout << "TShapes::Triangle: " << i << std::endl; }
};
struct TDerivedShapes : TShapes
{
virtual void Square(int i)
{ std::cout << "TDerivedShapes::Square: " << i << std::endl; }
};
struct TThings
{
void Thing1(int i)
{ std::cout << "TThings::Thing1: " << i << std::endl; }
static void Thing2(int i)
{ std::cout << "TThings::Thing2: " << i << std::endl; }
};
void Free(int i)
{ std::cout << "Free: " << i << std::endl; }
int main()
{
typedef Event<void, int> MyEvent;
MyEvent event;
TShapes shapes;
TDerivedShapes shapes2;
TThings things;
event += new MyEvent::T<TShapes>(&shapes, &TShapes::Square);
event += new MyEvent::T<TShapes>(&shapes, &TShapes::Triangle);
event += new MyEvent::T<TShapes>((TShapes*)&shapes2, &TShapes::Square);
event += new MyEvent::T<TDerivedShapes>(
&shapes2, &TDerivedShapes::Triangle);
event += new MyEvent::T<TThings>(&things, &TThings::Thing1);
event += new MyEvent::S(&TThings::Thing2);
event += new MyEvent::S(&Free);
std::cout << "<multicast>" << std::endl;
event(100);
std::cout << "</multicast>" << std::endl;
return 0;
}
This is what you should see when you run the program:
<multicast>
TShapes::Square: 100
TShapes::Triangle: 100
TDerivedShapes::Square: 100
TShapes::Triangle: 100
TThings::Thing1: 100
TThings::Thing2: 100
Free: 100
</multicast>
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.