A Portable Closure Implementation
Permission to read, use and copy this
documentation and accompanying software is hereby granted without fee, provided
that the above copyright notice appears in all copies and the contents remains
unmodified. Any recommended modification should be sent directly to the
author’s email address: tdemjen@yahoo.com.
The author makes no representations about the suitability of the accompanying software for any purpose. It is provided "as is" without expressed or implied warranty.
Note that this article comes with
free source code that you can download from here: events_source.zip
Download this as printable Rich Text Format document: events_rtf.zip
The source code was tested with Borland C++Builder 5, 6, Code Warrior 8.0 and Microsoft Visual C++ 7.0 under Windows 2000 Professional, and with GCC 3.1 under Gentoo Linux 1.2. Currently only the Events.h and v_Events.h files were tested under GCC. The testing of EventLists.h and v_EventLists.h is under progress. Note that Linux does not seem to have the __stdcall and __cdecl naming conventions, so several #ifdefs had to be (or will have to be) placed into the code. The final release is coming soon.
The closure is a highly efficient and flexible notification design pattern that dynamically links two functions or member functions to each other. It works very much like a conventional callback function, except that it can point to any member function of any class instance, thus giving a lot more possibilities than a regular function pointer. Unlike the old-style callbacks, closures communicate strictly between classes, without the need to write out-of-class global functions, and without performing suspicious parameter typecasts from void* or long to some class pointer.
Closures are most often used to implement generic event handling, such as a button click in a GUI, or a progress indication during a possibly long-lasting operation. Using closures, the designer of an event source doesn’t have to know anything about the event sink that is going to handle the notification. They can be developed independently, often by different programmers, or even using different compilers. Later these individual modules can be plugged together runtime as easily as a simple pointer assignment. Modules can be unlinked and re-linked to each other many times during the lifetime of an application. This design significantly increases the maintainability and reusability of a source code, and lets library developers create very open, yet easy-to-use modules.
In practice, closures are mainly used for implementing events. Therefore I am going to use the terms “event” and “closure” interchangeably throughout this documentation.
The only problem is that closures are very hard to implement without sufficient underlying linguistic support. A few respectable compiler vendors have recognized the importance of closures and implemented their own extensions to the C++ standard. Borland was the first company to come out with a C++-based closure implementation. The popularity and success of C++Builder, a powerful rapid application development tool, is lying in Borland’s excellent Property-Method-Event model, which would not exist without closures.
The closure in C++Builder is a great thing. It’s lightweight and fast. It would be nearly impossible to come up with a solution that runs faster or takes less memory than that. Projects using C++Builder closures, however, can not be compiled with any other development environment.
My goal was to create a very open, pure C++-based solution with all the advantages of Borland’s solution.
The closure is not an easy-to-understand concept, especially for those who have never used it before. Therefore I provided a sample code that demonstrates how closures work in Borland C++Builder, in order to help understanding the basic idea behind this paradigm.
Let’s say that you have a class named TButton that implements a button type of user interface control. This button has an event called OnClick, which will be fired whenever the user clicks on the button. Here is a possible implementation:
typedef void (__closure * TButtonClickEvent)(Button* Sender);
class TButton : public TGUIControl
{
public:
TButtonClickEvent OnClick;
void DoClick() { if(OnClick) OnClick(this); }
};
Note that this is just a sample code, Borland actually implemented TButton quite differently. In this example I just want to demonstrate the idea behind closures.
The reserved word __closure tells the compiler that our pointer will be a special pointer pointing to a class member function, instead of a regular global function.
Note that the button’s designer doesn’t have to know anything about the handler. The OnClick event can be handled anyhow and anywhere in the program. The only thing required is that the handler must be compatible with the TButtonClickEvent type, which means it must return void and must take a single input argument of type TButton*.
In most cases the button will be owned by a window, and the OnClick will be handled by the window object, like this:
class TMyWindow : public TWindow
{
public:
TMyWindow()
{
TButton* Button1 = new TButton(this, 10, 10);
Button1->OnClick = Button1Click; // assign event handler to event
}
private:
void Button1Click(TButton* Sender) // event handler
{
MessageBox(NULL, "Bingo", "", MB_OK);
}
TButton* Button1;
};
We create our button named Button1 in the constructor and assign the event handler right after that. The event assignment in C++Builder is similar to assigning a function pointer. The handler of the event receives a pointer to the sender, which is very convenient, because this way the handler knows which object sent the notification. It is not necessary to pass the sender, though.
In a much more general case, we have a sender class called TSender, and a receiver class called TReceiver:
struct TSender
{
TNotifyEvent OnNotify;
void Perform() { if(OnNotify) OnNotify(); }
};
struct TReceiver
{
void Handler() { /* handle event */ }
};
These classes are independent until they're linked. Now it is pretty easy to hook the sender to the receiver:
void Test()
{
TSender sender;
TReceiver receiver;
sender.OnNotify = receiver.Handler; // establish connection
if(sender.OnNotify) sender.OnNotify(); // fire the event
}
When the event is fired, the associated event handler will be called. It works exactly like a regular callback, except with __closure the callback is not a global function, but a member function of a class, any class. When you assign a handler to an event, you have to specify two things, the handler object and the handler function inside that object. The handler is always a certain function inside a certain object instance. In the above example, receiver is the object, and Handler is the member function.
Note how flexible this association is. You can connect the sender to the handler with a simple line of code. The connection can be unlinked anytime, and new connections can be established during runtime. The sender and the receiver can be in different modules, even if the individual modules are dynamically linked (DLLs under Windows or Shared Objects under Linux).
We are going to use a slightly modified version of this sample code throughout the entire documentation.
Before we go into the details of the implementation, I would like to stop for a moment to discuss what we expect from a good closure class.
We have the following requirements. First of all, it has to offer everything that Borland’s solution does. The closure has to hold a pointer to a class instance and a class member function at the same time. We must be able to assign, clear and re-assign event handlers to it at runtime. Any class should be able to send any event without restriction, and should be able to receive (handle) any number of events of any type.
We also wish it to be easy to use, preferably as close to the regular pointer notation as possible.
It should be a lightweight solution. We strive to keep the internal data overhead to an absolute minimum level, and to make the event propagation as fast as possible. Borland’s implementation is only slightly slower than a function call through a pointer, and only requires twice the size of a pointer – one that points to the member function, and another one that points to the object instance. It would be nice to support any number of event arguments, including a return type.
We know that closures can only be implemented as a template class. Therefore we understand that it is not going to work with every compiler, but it should work with the modern ones that follow the latest C++ standard.
Finally, let me bring up a very interesting idea. So far we talked about connecting one particular receiver to a sender. In many cases, however, you wish to notify multiple recipients at the same time. It would be very beneficial to support this concept.
As you will see, my closure implementation meets all the requirements I listed here.
We wish to implement a solution that is superior to conventional callbacks in every way. In order to do this, first you have to understand how the traditional solution works. With a little trick, we can use simple callbacks to act like a messenger between two classes, the sender and the receiver. I call this messenger function a “dispatcher”.
Consider the following example. You have a sender class that does not know anything about the receiver, other than its input argument(s), which is going to be a single integer named Arg1 in this case:
typedef void (*CallType)(void* UserParam, int Arg1);
class TSender
{
CallType Callback;
void* UserParam;
void Perform()
{
if(Callback)
for(int i = 0; i < 10; i++)
Callback(UserParam, i);
}
};
In this sample class, CallType is going to be used to simulate a closure with one input argument named Arg1. Note that we also provided an additional argument called UserParam, which can be used for any purpose the user wants to. It is just an additional parameter to pass custom information along with Arg1. If not used, we can simply set UserParam to NULL, as TSender does not care about its value.
TSender is a sample class that I use to demonstrate how to write an event source that is able to send notifications to any handler. Imagine that TSender::Perform performs something important and complex, like sending a notification about the progress of a file being compressed. Compression might take a long time, and therefore a progress indication could be important. In this example, we are just counting from 0 to 9 to simulate a progress of events.
You are supposed to assign an event handler to Callback before you call Perform, like this:
TSender sender;
sender.Callback = AnyHandler;
In case the event handler is a global function, we don’t have a whole lot to do:
void GlobalHandler(void* UserParam, int Arg1)
{
cout << Arg1;
}
The ultimate goal is of course to call back to another class’s member function. Let’s call our handler class TReceiver:
struct TReceiver
{
void Handler(int Arg1)
{
cout << Arg1;
}
};
TReceiver receiver;
All we have to do is write a callback that dispatches the notification to TReceiver::Handler:
void ReceiverDispatcher(void* UserParam, int Arg1)
{
reinterpret_cast<TReceiver*>(UserParam)->Handler(Arg1);
}
Now it’s time to assign this dispatcher function to the sender:
TSender sender;
sender.UserParam = &receiver;
sender.Callback = ReceiverDispatcher;
We are ready, because UserParam now points to the receiver object instance, and Callback points to the dispatcher, which casts the void* UserParam to the type of TReceiver* and dispatches the message to the handler of our choice.
Congratulations! You just did a perfectly working event notification! But I would not call this a nice solution, would you? It is not particularly impressive, to say the least. It requires a global dispatcher function that is totally out of the scope of object-oriented design. It also needs a highly dangerous forced typecast, which is definitely not a good programming practice.
It does the job, though, and does it really well. If you look back to the previous section, this solution actually meets many of the requirements we have set up. For example, the TSender class need not know anything about the event handler. We only used two pointers, which is quite a memory-conservative solution. It is technically impossible to do the job with a single pointer, unless we hard-coded the receiver pointer into the dispatcher. We could do that, but only by giving up most of the flexibility. Then we could not just assign any receiver to the sender. We assume that there might be lots of different instances of TReceiver, and we do not want to write a different dispatcher for each and every one of them. The whole idea of closures is that we can detach an event handler and attach another one anytime.
So why did we hard-code the handler member function’s name then? Well, there is a big difference between the member function and the object instance. There are only very few member functions. At least we can count them for sure, since we have to uniquely name every single one of them. Even if there are a lot of them, they are definitely countable. However, there could possibly be an unlimited number of object instances. They could be allocated on the heap with a new pointer in a ‘for’ loop. It is simply not safe to assume that there are only a few, a predefined number of object instances. The entire closure idea is all about flexibility, so we must keep the object pointer a user-specified parameter, otherwise we would not meet the requirements we have previously set up.
As you will see, our final solution will be based on the same idea as the example program we just discussed.
The implementation of my closure uses advanced programming concepts and several interesting tricks. Do not be intimidated by this, as you don’t have to fully understand every detail in order to use my classes in your application. Now we are going to discuss the basic ideas I used to develop my solution.
At this point I would like to note that I have two different solutions for implementing closures. They use exactly the same amount of memory. Let me begin the discussion with the one that runs faster and is easier to implement. At the end of this documentation, I am going to show you the other solution as well.
In the previous section I explained how to use a traditional callback function configured in such a way that it can dispatch a notification to any member function of any object instance. Although the solution’s performance is very impressive, it is based on programming concepts that most people would not call modern. It requires a lot of tedious typing and a very suspicious pointer conversion that leaves room for a lot of possible errors.
We would solve both problems if we could teach the compiler how to generate the correct dispatcher callback automatically. Fortunately there is a way to let the compilers do this kind of thing by itself with using templates.
If you are familiar with templates, you know that template arguments are usually classes, but they can also be constant numeric types, such as integers or global pointers. Anything that evaluates to a constant integer expression at compile-time can be used as a template argument. It is not a well-know fact that templates can even be specialized by pointers to member functions, and it work with most compilers. It is a little tricky, here is how to do it:
template <class C, void (C::*F)()>
struct TDispatcher
{
C* p;
TDispatcher(C* p) : p(p) { }
void Call() { (p->*F)(); }
};
struct TReceiver
{
void Print() { cout << "Hi"; }
};
Unfortunately it requires two template arguments, the class itself and a pointer to the member function of the same class. The compiler could not figure out the class’ type if we didn’t specify it separately. And here is how to set up a link between the two classes:
TReceiver receiver;
TDispatcher<TReceiver, TReceiver::Print> dispatcher(&receiver);
dispatcher.Call(); // this calls receiver.Print();
Again, you must specify the class name twice, but it works. A template specialization does not require any memory allocation in the code, since it is always resolved by the compiler at compile time. There is no penalty to be paid for this during runtime. It means we simply do not have to store the member function pointer in the closure class as a member variable. The compiler is going to take care of it at compile time.
Let’s go back to the global callback function concept and wrap that solution into a template. We are going to make the callback function itself a template function that can be specialized by a member function pointer, like this:
template <class C, void (C::*F)()>
static void DispatcherFunc(void* p)
{
((reinterpret_cast<C*>(p))->*F)();
}
The function should be static, since we do not expect it to be called from other modules. It is using the same kind of reinterpret_cast trick as our traditional callback example, except this time it is not dangerous, because this all is going to be wrapped into a class, and the compiler is going to generate the correct type of callback template specialization automatically. It is all hidden from the user. We just have to be careful when writing the event class.
Here is the simplified version of the closure. Note that this one does not have any input arguments:
class event
{
private:
void* p;
typedef void (*DispatcherType)(void* p);
DispatcherType dispatcher;
template <class C, void (C::*F)()>
static void DispatcherFunc(void* p)
{
((reinterpret_cast<C*>(p))->*F)();
}
public:
template <class C, void (C::*F)()>
void Connect(C* cp)
{
p = cp;
dispatcher = DispatcherFunc<C, F>;
}
void operator()()
{
(*dispatcher)(p);
}
operator bool() const
{
return dispatcher != NULL ? true : false;
}
};
I am going to explain how this class works shortly, but first I would like to show you an example for its usage:
// the sender side:
struct TSender
{
event OnNotify;
void Perform() { if(OnNotify) OnNotify(); }
};
// the receiver side:
struct TReceiver
{
void Handler() { cout << "I have been notified"; }
};
// linking up the sender to the receiver:
void Test()
{
TSender sender;
TReceiver receiver;
sender.OnNotify.Connect<TReceiver, TReceiver::Handler>(&receiver);
sender.Perform();
}
This solution has a slightly different syntax than Borland’s solution, but the difference is only noticeable in the line that connects an event handler to an event. Other than that, this closure class is compatible with Borland’s closure syntax, and also with the regular callback pointer syntax. It is technically impossible to fully emulate the regular callback pointer notation, because when an event handler is assigned, two pointers have to be assigned, not just one. The assignment operator, however, does not support two input arguments. Not to mention the need for an explicit template specialization. The compiler simply can not find out the correct type of specialization for you, therefore you must explicitly specify that at the Connect function call.
Let’s get back to the event class. It has basically two member variables, just like our traditional callback example. One of the members is a void* pointer that stores the class instance, which we named here “p”. The other member is a pointer to a dispatcher. The dispatcher has a void* parameter, which is again a pointer to the receiver class instance.
In order to be compatible with the callback syntax, I had to override the operator(). It could not be any simpler that this. All it does is calling the dispatcher, by passing p, the receiver class instance, to it.
The most important function is the Connect, which sets up the two member variables for the class. In addition to that, it also instructs the compiler to generate the proper type of dispatcher. The member variables will automatically point to the correct callback function, since Connect has the same template arguments as the callback (namely, C and F).
The dispatcher function can be either a static private member of the class, or a static global function. I prefer to make it a private member of the event, although this is not working well with every compiler.
Finally, here is how this closure class works. It has a void* pointer that specifies the receiver class instance. It is generic enough, since void* can easily be cast to any other pointer type. It would normally be dangerous, but it is a private member, so it is inaccessible from the outside. The event class has one more pointer that points to a dispatcher function, which is generic enough, because it can point to any dispatcher having the correct signature. The class does not have any other member variables. It means this implementation is generic enough, it can hold a pointer to any possible event handler. The Connect function is responsible for generating and assigning the correct dispatcher, thus binding the handler to the event. But this bond can be broken anytime, since it is just based on two pointers that can be reassigned to something else if needed.
A question arises here: If we already managed to specialize our template with a member function pointer, why can’t we substitute the void* p pointer inside the event class with a template argument, thus saving some memory? Of course this would require a new pointer template argument to be added to the dispatcher and to the Connect function as well. In that case our sample code would look like this:
void Test()
{
TSender sender;
TReceiver receiver;
sender.OnNotify.Connect<TReceiver, TReceiver::Handler, &receiver>();
sender.Perform();
}
Now if ‘receiver’ was a global pointer, available to all modules, it would work. But object instances are rarely if ever application-global. The above sample code would not compile, because ‘receiver’ is a local pointer, therefore it always has a different address every time the Test() function is executed. A template specialization can only be based on expressions known during compile time. For example, ‘159’ is a good candidate, or TReceiver::Print is also fine, but ‘receiver’ doesn’t have an absolute global address, therefore it can not be used for template specialization.
The bottom line is that we must keep the void* p pointer in the closure class, in order to have a flexible design.
Unfortunately, Visual C++ 7.0 does not like that DispatchFunc is a member function. It only compiles if we take that function out and make it global. Visual C++ believes that a member function is not compatible with a global function, which is normally true, but static member functions should be compatible with global functions. This is what Visual C++ is overlooking during the template specialization. I wanted to get it to work on that compiler, and there was no other way but to make DispatcherFunc static global.
Obviously the actual implementation of the class is a little bit more complicated and the class itself is a template also, so you can control the exact number and types of the event arguments and the return type. Note that Microsoft Visual C++ does not handle the template specialization correctly. The problem leads to the fact that the compiler can not handle return types other than void. It is not a big restriction, since you can use a reference-type input argument to return values through an event. Please keep in mind that if Visual C++ compatibility is important for you, your events must always return void. On the other hand, I have another version of my events which works totally differently, and that one supports non-void return types even under Visual C++. Please see the end of this documentation for more information.
The full implementation of event also has a function called Disconnect() to clear the current assignment from the event (similarly to assigning NULL to a regular pointer). In addition, I provided a special Connect function to assign global callback functions to the closure. The class also defines an operator< for sorting, so they can be compared like a normal function pointer. I am going to cover the exact purpose of this a little bit later on.
Now that you understand the idea behind the implementation, it is time to see how my events work in practice.
In order to use the events, you have to include “events.h” from your CPP file. Events.h only includes EventDef.h internally; absolutely no other files are included. You should never include EventDef.h itself, as you are going to get a whole bunch of compiler errors. That file was not intended to be included alone.
I used templates to make the event arguments generic. This way an event can pass any information to the handler, while the compiler can still validate if a certain type of event is assigned to a compatible event handler. This is always enforced by the compiler. For example, you can not handle an event that sends an int by a handler that expects a string.
An event is a template class with variable number of template arguments. Since the C++ language currently does not allow us to use variable template argument lists, I created several different event templates:
template <class R> class event0;
template <class R, class P1> class event1;
template <class R, class P1, class P2> class event2;
template <class R, class P1, class P2, class P3> class event3;
template <class R, class P1, class P2, class P3, class P4> class event4;
...
where R is the return type, and P1 … P6 are the input argument types. An event can be treated as a special callback function that returns type R and has several input arguments, such as (P1 … P6):
R (*)(P1, P2, P3, P4, P5, P6);
You must exactly know what type of information you would like to pass to the handler and how many arguments you need. Should you need to pass more than 6 arguments, you will want to consider passing a reference to a structure instead. Note that the internal implementation requires an addition function call, therefore it is absolutely essential that you pass bigger structures and classes by reference or pointer, never by value. Never pass a map or a big vector by value.
Each event can have a corresponding event handler, which can be a member function of any class, or simply a global function. A handler can only handle events compatible with the handler, and each handler can only be assigned to compatible events.
Although events are complex template classes, they have close-to-pointer syntax. Events can not be treated 100% like a pointer, but they are very close to that.
Consider the following example. You have a user interface class that is able to send a mouse click notification to anything that is able to handle it. In this case, the mouse click notification is an event object owned by the sender. The event works like a pointer to a callback function, although internally it is more than a single pointer. Then there is another class, the Application Manager, which is able to handle a mouse click coming from a user interface entity. We are going to call this function the event handler.
Note that the event does not know anything about the handler. The person who designs the user interface item does not know who and how is going to handle it. The programmer who is designing the handler does not necessarily know the source code for the sender. There is only one thing in which they both have to be consistent, and it is the exact number and types of parameters they have. Whatever parameters the event has, the handler function must have exactly the same type of arguments, otherwise they are not compatible.
Here is an example class that is able to send an event:
class TButton
{
public:
typedef event1<void, TButton&> TButtonClick;
TButtonClick OnClick;
};
This means that TButtonClick is an event that returns void and has 1 parameter, a TButton&. It means this type of event can be plugged into a global handler function like this:
void Handler(TButton& Sender);
Here is how to make the association between the sender and the handler:
TButton Button1;
Button1.OnClick.Connect(Handler);
From this moment, Button1.OnClick points to the Handler function. Button1.OnClick(Button1) will call Handler and pass Button1 as its parameter. This way the handler will know who sent the event.
The big thing, however, is that you can assign any member function to the event. Let’s connect something else to OnClick:
class TAppManager
{
public:
void Button1Click(TButton& Sender);
};
TAppManager AppManager;
Button1.OnClick.Connect<TAppManager, TAppManager::Button1Click>(AppManager);
This tells the compiler to specialize the event with TAppManager::Button1Click, and store a pointer to AppManager. So if we call Button1.OnClick(Button1) after the assignment, the AppManager.Button1Click function will be notified.
Once a handler is assigned to an event, the event can be treated as a pointer. You can, for example, copy an event:
TButton Button2;
Button2.OnClick = Button1.OnClick;
In this case, Button2.OnClick will point to the same handler as Button1.OnClick does. Note that they are independent objects. So if Button1.OnClick changes, it is not going to affect Button2.OnClick. This is because Button1 and Button2 have their own copies of the even object.
Calling an event has the same syntax as calling a function or function pointer:
Button1.OnClick(Button1);
This looks exactly like a simple function call. You have to be careful, however. It is possible that the event is not assigned to any handler. Just like you would test a pointer if it is not NULL, you should check an event for validity:
if(Button1.OnClick) Button1.OnClick(Button1);
This is very similar to the way Borland C++Builder is handling events:
if(Button1->OnClick) Button1->OnClick(Button1);
Of course you can use object pointers too. You can also detach a handler from the event by calling the Disconnect function:
Button1.OnClick.Disconnect();
This will make the event empty, pointing to nowhere, like a NULL pointer. It is important to note that if you create an event object and do not initialize it, it is going to point nowhere by default. Unlike a pointer, an event is always initialized by default to NULL, even if you do not initialize it explicitly. In other words, you do not have to worry about initializing events. There’s nothing to forget about!
Although events can be very useful, they only link a class (the sender) to one function or member function (the receiver). In many cases, there is a need to notify or query multiple event handlers. Therefore I introduced another concept called the event list, which is able to distribute a simple function call to multiple recipients.
Event lists are yet another set of template classes named event_list0, event_list1, ..., event_list6. Just like events and event handlers, they must always be compatible. For example, event_list1<int, const std::string&> can not distribute events to handlers with a signature of int Function(int).
Event lists are very simple. Although their name suggests they are lists, internally they do not use an STL list container. I used std::set as an underlying implementation, so that events can be searched quickly. This is necessary, because we want a way to quickly add and remove events from the list, which is also called event registration and unregistration.
It is pretty easy to use an event list. The syntax is very similar to that of the events, except they do not have to be checked whether a valid handler is assigned to them. You can assign any number of handlers to an event list, and call all of them at once. I have to agree, the syntax is a bit complex, but don’t let it turn you down. This example attaches two handlers to an event list:
TTestHandler h1, h2;
event_list1<void, const TPoint&> El;
El << event1<void, const TPoint&>().
Connect<TTestHandler, TTestHandler::Handle>(h1)
<< event1<void, const TPoint&>().
Connect<TTestHandler, TTestHandler::Handle>(h2);
El(TPoint(10, 50)); // h1.DoHandle and h2.DoHandle will be called
When you “call” an event list with the function call operator, you actually call all the registered event handlers one by one. If there is nothing registered, nothing will happen.
The event_list1 template class is defined as follows:
template <class R, class P1>
class event_list1
{
public:
typedef event1<R, P1> EventType;
bool AddNotification(const EventType& e)
{ return items.insert(e).second; }
void RemoveNotification(const EventType& e)
{ items.remove(e); }
void operator()(P1 p1) const
{
for(Container::const_iterator i = items.begin(); i != items.end(); ++i)
(*i)(p1);
}
event_list1& operator<<(const EventType& e)
{ AddNotification(e); return *this; }
event_list1& operator>>(const EventType& e)
{ RemoveNotification(e); return *this; }
private:
typedef std::set<EventType> Container;
Container items;
};
Now it is clear why event1 defines a “<” operator – it is necessary for the std::set. You can not insert items into a set that doesn’t have an operator<.
The core of the class is operator(), which simply iterates through the set and calls each event. Use the << operator or AddNotification to add an event handler to the list. Use the >> operator or RemoveNotification to remove an event from the list.
Note that since event_list is based on std::set, the order of the call is undefined. The event handlers will not necessarily be called in the same order as they are added to the list. Usually it does not matter, since the event list is independent from the event handlers, and the handlers do not know about each other. However, if the order is important for you, you must store the events in a vector, instead of a set.
So far in this documentation we have discussed a complete closure solution that uses conventional callback functions as an underlying technology. However, there is yet another way to implement closures – using virtual functions.
As you have seen before, a closure is nothing else but an object that forwards a notification to a certain member function of a given class. The core problem here is that we have to be able to perform an operation that we don’t know anything about. But the C++ language already has a solution for that problem – it’s called the virtual function. Here is a sample code that demonstrates the basic idea:
struct UnknownCall
{
virtual void Call() = 0;
};
UnknownCall* OnCall;
class CallDispatcher : protected UnknownCall
{
public:
CallDispatcher(MyClass* p) : p(p) { }
MyClass* p;
protected:
virtual void Call() { p->MyHandler(); }
};
UnknownCall is an interface for a calling mechanism that delivers the notification to the recipient. CallDispatcher is a specific implementation of this class, an intermediate layer that forwards (dispatches) the call from the sender to the actual event handler. OnCall is a pointer that links the sender to a specific dispatcher.
It is only a single line of code to set up a link between the sender and the receiver:
MyClass receiver;
OnCall = new CallDispatcher(&receiver);
The big problem with this implementation is that it requires the user to type in a long line of code that forwards the sender’s call to the receiver. Fortunately we can use the same trick here that worked fine previously in the callback-based implementation. Let’s use a member function pointer type of template, like this:
template <class C, void (C::*F)()>
struct TDispatcher : public UnknownCall
{
C* p;
TDispatcher(C* p) : p(p) { }
virtual void Call() { (p->*F)(); }
};
struct TReceiver
{
void Print() { cout << "Hi"; }
};
Here is how to set up a link between the sender and the receiver:
void Test()
{
TReceiver receiver;
TDispatcher<TReceiver, TReceiver::Print> dispatcher(&receiver);
dispatcher.Call(); // this calls receiver.Print();
}
Again, you must type in the class name twice, but it works. A template specialization doesn’t require any memory allocation in the code, since it is always resolved by the compiler at compile-time, instead of runtime.
By wrapping the entire code into a single class, we can finish the basic version of the closure pretty easily, as shown here:
class event
{
private:
struct dispintface
{
virtual void Execute() const = 0;
};
template <class C, void (C::*F)()>
struct dispatcher : public dispintface
{
C* p;
dispatcher(C* p) : p(p) { }
void Execute() const { (p->*F)(); }
};
dispintface* d;
public:
template <class C, void (C::*F)()>
void Connect(C* p)
{ d = new dispatcher<C, F>(p); }
void operator()() { d->Execute(); }
};
The event class has two internal class declarations that are invisible to the outside world. Dispintface is an interface class with a pure virtual function called Execute, and dispatcher is an implementation of dispintface that calls a certain member function (C::*F) of a certain object (C* p). The event class stores a pointer to a specific dispatcher implementation that you set up by calling the Connect function. I overloaded the operator() in order to provide a function call notation for the event, as if it was a real function pointer.
We are not finished yet, still there are many details to be elaborated, but I wanted to keep this first version very simple. Here is a sample code that demonstrates the usage of the event class:
struct TReceiver
{
void Print() { Form1->Caption = "Hi"; }
};
TReceiver receiver;
event e;
e.Connect<TReceiver, TReceiver::Print>(&receiver);
// connect
receiver.Print to event 'e'
e();
// call the event as you would call a function
This event class works very much like a regular function pointer, except that you must call an explicitly specialized function named ‘Connect’ to set it up. We could not possibly use an assignment operator to assign member functions, since you actually have to assign two things at the same time, a pointer to an object, and a pointer to a member function. The assignment operator can only have one argument, so there is no way we can use it to set up an event. Other than that, events can be used exactly like a function pointer.
I would like to point out that the current code uses much more memory than necessary. Although we could not (and would not like to) eliminate the C* p pointer, there is still one pointer that is absolutely not needed. When I developed my first event class, I was positive that the “dispintface* d” pointer in the event class was necessary and could not be eliminated. I was wrong.
The entire closure implementation is based on polymorphism, and polymorphic objects are supposed to be created by the new operator, at least when they are assigned to a polymorphic pointer. That’s true. On the other hand, many programmers mistakenly believe that the new operator will always allocate memory on the heap. In C++ there’s an overloaded version of the new operator, which lets you specify an already existing chunk of memory where the object will be created. Of course this is very dangerous, because it can be catastrophic if there is not enough memory available for the object. Here is an example that demonstrates how to create an object over an existing piece of memory:
#include <new.h>
struct MyPoint
{
int x, y;
~MyPoint() { cout
<< "destruct"; }
};
void Test()
{
unsigned char Mem[sizeof(MyPoint)];
// this works, at least on Intel processors
MyPoint* p = new (Mem) MyPoint;
p->x = 10;
p->y = 20;
p->~MyPoint();
}
In this example, we created a pointer of type MyPoint in the place of a variable in the stack, using the new operator. Note that we had to include new.h for this. Also note that it is important to explicitly call the destructor, and we must not call the delete operator at all.
Needless to say, we must be extremely careful if we want to use this idea, but it is worth the extra effort. Note that the implementation shown in the previous section requires 3 pointers when an event handler is assigned. One for the dispatcher* pointer, one for the C* pointer, and the third one is the hidden VMT pointer implicitly created by the compiler. That is the price to be paid for using virtual functions in a class. Every single event takes that amount of memory, and a class might have a lot of events. So it is worth adding a little trick to the existing code in order to eliminate the dispatcher* pointer altogether.
Although the event class is using polymorphic objects, the size of the descendant classes is very well known. Let’s see how much memory the dispatcher requires. It needs one implicit pointer for the VMT, which is not visible in the source code, but we know it’s there. Every class having virtual functions has a pointer to the VMT. In addition, there’s the C* p pointer, which are 2 pointers altogether. So let’s just pre-allocate space for 2 pointers in the event class. This requires significant modifications to the event class, as shown in Listing 3.
class event
{
private:
void* PlaceHolder[2];
protected:
const dispintface* GetPtr() const
{ return reinterpret_cast<const dispintfacee*>(PlaceHolder); }
public:
event() { PlaceHolder[0] = NULL; }
~event() { GetPtr()->~dispintface(); }
template <class C, void (C::*F)()>
void Connect(C* p)
{ new (PlaceHolder) dispatcher<C, F>(p); }
void operator()() const { GetPtr()->Execute(); }
operator bool() const { return PlaceHolder[0] != NULL; }
};
The basic idea here is that we don’t need to store a pointer to the dispintface anymore, because it is stored right inside PlaceHolder, which is a constant expression, and therefore its address is always well known by the compiler.
Note that I intentionally omitted the two internal classes, because they did not change. I added a bool conversion operator, so you can check the assignment, very much the same way you test for a NULL pointer. Also note that the Connect function is not perfect, because it’s not calling the destructor for the old instance before assigning a new one, but I wanted to keep this explanation relatively easy to understand. The actual implementation is slightly more sophisticated.
There is yet another important feature in my closures. I wanted to add support for calling global functions as well, not just class member functions. So I created a special dispatcher called callback, which roughly looks like this:
struct callback : public dispintface
{
typedef void (__cdecl *PF)();
public:
callback(PF f) : f(f) { }
virtual void Execute() const { f(); }
private:
PF f;
};
It is simpler than “dispatcher”, it doesn’t require any template specialization. The overloaded Connect function for this is also a lot simpler, and doesn’t require explicit template specialization to be called:
void Connect(callback::PF f)
{ new (PlaceHolder) callback(f); }
The actual implementation is just a little bit more sophisticated.
I have spent a tremendous amount of time experimenting with closures, and came up with two different solutions. The first one I developed is based on virtual function calls, therefore I simply call that “virtual closure”. Later I realized that the virtual function call can be eliminated by using regular callbacks. That solution is named “callback-based closure”.
I wanted to keep the possibility of using the two solutions independently at the same time in the same application. Therefore I named the two sets of classes slightly differently. The callback-based event classes are simply named event0, event1, event2, etc. They are declared in Events.h. The virtual function-based classes are named v_event0, v_event1, v_event2, etc. They are declared in V_Events.h
The two solutions basically use the same syntax. In most of the cases, you can use any of the solutions interchangeably. From the user’s standpoint, the only noticeable difference is the 'v_' in the naming.
They work, however, totally differently, and therefore they are not byte compatible. If a DLL is using the callback version, the main application should also use the callback version. If a DLL is using the virtual version, the main application should use that too. It is very important, because you will have to include the Events.h file from every module, and if the sender is using one of the event types, the receiver should use the same one. Otherwise your application will crash.
The virtual version is considerably more complicated and probably a lot harder to understand. It runs slightly slower too. On the other hand, it does not have any problem with Visual C++. The closure version caused me a lot of trouble with that compiler, and I had to make a specific implementation for Visual C++, which is partly different from the implementation intended for standard C++-compatible compilers. But the two implementations of the callback solution are truly compatible. It means an application can be written in C++Builder, while the DLL can be done with Visual C++, and their events will link very nicely.
However, under Visual C++, the callback-based implementation does not accept return types, it must always be void. This is the only major restriction that I know of. Other compilers do not have this problem, and the virtual function-based solution does work with any return type even under Visual C++.
Note that not all the compilers are fully standard compliant. I can not guarantee that either version of my closure classes will work with your system. Compilers tested so far:
Borland C++Builder 5.0, 6.0: no problem
Visual C++ 7.0: no problem, but the callback solution requires R = void
GCC 3.1 under Linux: it works nicely too
ICC (Intel C Compiler) under Windows: Pavel Vozenilek confirmed it works
Code Warrior 7.0: the compiler has a bug that doesn't allow the use of member function pointer type of template arguments.
Code Warrior 8.0: it works fine
The events were designed for non-const handler functions. It is necessary to create yet another set of classes for the const handlers.
Using closures we can uncouple a specific event handler from the generic event source. The sender and receiver classes do not have to know anything about each other, except the number and type of input arguments and the return type. The event itself can be placed in a class inside a DLL, while the event handler can be in another DLL or in the main application. It is doable, because the event and the handler can be compiled independently, and later linked together through a pointer. The only code that has to be duplicated in both modules is the event class itself.
If there is a chance that multiple handlers will be notified or queried through events, use the event lists to notify all the handlers at once. Handlers can be added and removed from the list anytime. Note that in my implementation the order of the invocation of the event handlers is undefined.
Note that this article comes with free source code that you can download from here: events_source.zip
Thanks to Pavel Vozenilek for his testing, bug report and interesting ideas that will be discussed and implemented soon.