One more degree of freedom for C++
Those who worked enough with C or other procedure oriented languages know how much flexibility callbacks provide. The simplest example is the qsort function of the C standard library. It is also not unintentional that many libraries, windowing system APIs and operating system APIs also highly rely on callbacks to pass a particular task over to another program module and it is one of the fundamental tools needed to implement an event-driven application. At the same time, object oriented languages does not directly support the concept of callbacks as they don’t really fit into the paradigms used by these languages. Fortunately, even if not as a language feature, all object oriented languages support a similar facility like callbacks in the form of delegates.
Delegation as a design pattern is used to describe the situation when one object passes on the implementation of a particular task to another object. This clearly reflects the purpose of callbacks used in procedure oriented languages. Many languages does natively support some form of delegation, some of the well known ones are C# and Delphi.
Callbacks
As mentioned before, the facility present in procedure oriented languages that enables the delegation of functionalities to other modules is done with callbacks. These callbacks are specified by passing function pointers to some registration functions provided by the library. Here is a very simple C example:
/* server header */
void registerFooCallback(int (*fooCB)(int, float));
int doFoo(int a, float b);
/* client code */
int myFooCallback(int a, float b) {
/* ... do something ... */
}
int main() {
registerFooCallback(myFooCallback);
cout << doFoo(5, 3.2f);
return 0;
}
Here we can see how easily callbacks provide injection of user code for handling events happened in the server.
Delegation as a design pattern
The simplest way to create object oriented callbacks is by applying the design pattern of delegation. If we would like to construct the C++ equivalent of the example above using the mentioned pattern, we end up with something like the following:
/* server header */
class IFooCallback {
public:
virtual int operator() (int a, float b) = 0;
};
class Foo {
private:
IFooCallback* _fooCB;
public:
void registerCallback(IFooCallback* fooCB);
int doFoo(int a, float b);
};
/* client code */
class MyFooCallback: public IFooCallback {
int operator() (int a, float b) {
/* ... do something ... */
}
};
int main() {
Foo foo;
MyFooCallback fooCB;
foo.registerCallback(fooCB);
cout << foo.doFoo(5, 3.2f);
return 0;
}
As you can see, it is quite straightforward to provide an object oriented alternative to callbacks. However, there is a very significant drawback when using the technique above, namely the type intrusion inherently coming from this definition of a callback. The client code needs to explicitly inherit it’s own code from a type defined in the server. This results in tight coupling and is likely to carry other disadvantages inside regarding to maintainability and migration issues.
Delegate methods
In our previous attempt to provide an easy to use C++ alternative for callbacks with OOP in mind we tried to replace function pointers with a pure virtual base class that acts like an interface definition for our callback. However, it somewhat violates the original goals of delegates which by definition should be some form of run-time inheritance (this varies from definition to definition, still, this is the one that I’m referring to in this article). We soon figure out that the most convenient way would be to be able to assign member functions of any class as a callback. Obviously, the parameters and return type should still match as previously to provide type safety, but we would like to remove any additional dependencies between the client and the server.
While C++ does have the term of pointers to member functions there is no easy and standard way to implement callbacks using them. Or is there? First of all, there is no particular problem with class static member functions as they are much like C functions, however, limiting delegates to static methods heavily affects the freedom of the developer. The problem with object member functions and especially with virtual member functions is that they have the implicit parameter this that enables them to access the object they correspond to.
The popular Boost library provides mechanisms that enables the use of object member functions as separate entities by using the bind functor adaptor which became part of the language standard as part of Technical Report 1. This extension makes it possible to use member functions as delegates in a way that does not involve any type intrusion side effects.
Unfortunately, these facilities involve a noticeable performance hit when the callback is invoked compared to simple method invocations. Also, using functor adaptors for implementing delegates is not the most straightforward and makes the code quite ugly compared to an ideal situation when delegates are part of the language itself. Of course, this is only my opinion, others who used these libraries more often may have a different vision about the topic.
Anyway, as for me performance is always a concern, I started to look around for alternatives. It surprised me that I’ve found even two of them very soon:
- Fastest Possible C++ Delegates by Don Clugston – This is a library that provides delegates that are as fast as simple virtual method invocations. The implementation strongly relies on the behavior of different compilers, yet is very portable, at least as far as I can tell.
- The Impossibly Fast C++ Delegates by Sergey Ryazanov – This library was introduced as an alternative to the previous one that strictly relies only on standard features of the languages. Surprisingly, this later is less supported by different compiler implementations and it is also somewhat slower than the previous one.
Personally, I go with the first one as for me performance and portability is more important than conformance with the standard. And, of course, it is not that hard to change the back-end for the delegate support at some time if I change my mind. Finally, lets see how our foo callback looks like when using the fast delegates of Don Clugston:
/* server header */
class Foo {
private:
FastDelegate2<int, float, int> _fooCB;
public:
void registerCallback(FastDelegate2<int, float, int> fooCB);
int doFoo(int a, float b);
};
/* client code */
class MyClass {
virtual int handleFoo(int a, float b) {
/* ... do something ... */
}
};
int main() {
Foo foo;
MyClass myObj;
foo.registerCallback( MakeDelegate(&myObj, &MyClass::handleFoo) );
cout << foo.doFoo(5, 3.2f);
return 0;
}
Multicast delegates
The delegates presented previously can only be bound to a single method, as usually delegates behave this way, although a single method can be bound by many delegates. The signals and slots model extends this to a many-to-many relationship. Thus a signal is actually just a delegate that can bind to multiple methods at once. Such a primitive is sometimes also referred to as a multicast delegate.
Multicast delegates come handy especially in case of user interface programming and other situations where the event based programming model is used. The basic foundation behind this programming model is the idea of “subscribe and notify”. That means there are publishers who will do some logic and sometimes publish events. When such an event is published, it is actually sent out to the subscribers who have subscribed to receive the specific event. At implementation level this is nothing more than having a multicast delegate in the publisher object and providing an interface that will be used by the subscriber objects to register one of their methods that has to be called in case a particular event occurs.
There are plenty of signals and slots libraries out there including but not limited to the Boost Signals library. However, again, if performance is a concern one must look around carefully to find the appropriate library suitable for a particular purpose. One such library that extends the fast delegates of Clugston with a signals and slots framework is that of Patrick Hogan’s.
Asynchronous delegates
If we do one more step forward, we arrive to asynchronous delegates that can provide us a flexible yet efficient messaging system for multi-threaded applications. The only additional thing we have to implement a message queue on the callee side and optionally some form of synchronization if we would like to also make it possible for the asynchronous delegates to return data to the caller.
As this topic deserves a thorough discussion on its own, I would recap on the subject in a future article and try to provide a sample implementation using OpenMP as usual.
Conclusion
We’ve just touched the surface of what possible use case scenarios of delegates one can met during software development, still, we’ve seen how many advantages such a programming primitive can give to C++ developers no matter if they are implementing a very simple library of sorting algorithms like the qsort C standard library function or a robust, fully event-driven multi-threaded application. We’ve also seen that there exist several efficient implementations of such a framework for those performance fanatics like me.
It is a perfect example how easily one can extend C++ with another facility that is usually available only in the most modern managed languages. By the way, I would be interested in your opinion what do you like the most in other languages like Java and C#, and you are disappointed that C++ does not directly provide the same thing. Maybe there exists a C++ alternative for those facilities as well, just we have to look around to find them…
No comments yet.
176No trackbacks yet.
Instance Cloud Reduction reloaded
about 1 month ago - 1 comment
A few months ago I’ve presented an object culling mechanism that I’ve named Instance Cloud Reduction (ICR) in the article Instance culling using geometry shaders. The technique targets the first generation of OpenGL 3 capable cards and takes advantage of geometry shaders’ capability to reduce the emitted geometry amount in order to get to a [...]
Flexible static analysis for C++ code bases
about 5 months ago - No comments
The importance of static code analysis is already a well known thing in the domain of software development. There are plenty of useful and less useful tools for the purpose, especially in the case of C++. However, even if in general the quality of these softwares is adequate they usually suffer from the inability for [...]
Unit testing OpenGL applications
about 5 months ago - 22 comments
Nowadays comprehensive testing is a must for any software product. However, it isn’t such a general rule when it comes to graphics applications. Many developers face difficulties when they have to test their rendering codes. Manual tests and visual feedback is sometimes satisfactory but if one would like to have automated regression tests usual approaches [...]
Instance culling using geometry shaders
about 5 months ago - 34 comments
Since the appearance of Shader Model 4.0 people wonder how to take advantage of the newly introduced programmable pipeline stage. The most important feature enabled by geometry shaders is that one can change the amount of emitted primitives inside the pipeline. The first thing that a naive developer would try to do with it is [...]
Synchronizable objects for C++
about 5 months ago - 6 comments
Previously I talked about how one can easily take advantage of multiprocessing using OpenMP. Even if the C pragmas introduced by the parallel programming API standard is very straightforward for simple programs, it simply doesn’t fit nicely in a complex C++ application that is built from the ground with the OOP in mind. To smoothly [...]
Flawless alternative to SDL
about 6 months ago - 4 comments
There was always big need for libraries that provide an abstract interface towards the basic platform specific facilities that are necessary for setting up an execution environment for a particular application. In the OpenGL world one of the first such libraries was GLUT. After a while more and more functionalities were put into these libraries [...]
Never seen flexibility for Delphi
about 6 months ago - No comments
As far as I can tell people still prefer using python for scripting in their Delphi projects. This is probably because python scripting in Delphi has a long history and it is a proven method with easy integration. What if I would tell you that there is a free solution which enables you to write [...]
Exploit parallelism with the least effort
about 6 months ago - 2 comments
Multiprocessing has been there for decades as a premium feature for enterprise applications but adopting this technology still brings huge burden to software companies that still maintain and develop legacy code. Nowadays, as most commodity hardware already have highly parallelized architectures, a modern application is almost unimaginable without proper multi-threading capabilities even if we talk [...]
Unit testing in C++
about 6 months ago - 2 comments
Many people are looking for information about which particular C++ unit testing framework they should use for their project and there are also many articles discuss the topic but few articles talk about mock frameworks which are even more important factor when applying unit testing in practice and they have much greater effect on the [...]
Manage code yourself
about 6 months ago - 8 comments
Those who know me know it well that I am not a big fan of languages which produce managed code. In this article I would like to cover the reasons behind my skepticism. Also I would like to dispel the myths around such languages and try to prove them with facts (we will see how [...]
