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…
| Print article | This entry was posted by Daniel Rákos on February 14, 2010 at 2:38 pm, and is filed under General, Programming. Follow any responses to this post through RSS 2.0. You can leave a response or trackback from your own site. |
No comments yet.
No trackbacks yet.
Frei-Chen edge detector
about 2 years ago - 16 comments
In this article, I would like to present you an edge detection algorithm that shares similar performance characteristics like the well-known Sobel operator but provides slightly better edge detection and can be seamlessly extended with little to no performance overhead to also detect corners alongside with edges. The algorithm works on a 3×3 texel footprint
Suggestions for OpenGL 4.2 and beyond
about 2 years ago - 29 comments
The Khronos Group did a great job in the last few years to once again prove that OpenGL is still in game and that it can become the ultimate graphics API of choice, if it is not that already. However, we must note that it is not quite yet true that OpenGL 4.1 is a
GPU based dynamic geometry LOD
about 2 years ago - 6 comments
Dynamic geometry level-of-detail (LOD) algorithms are very popular and powerful algorithms that provide a great level of rendering performance optimization while preserving detail by using less detailed geometry for objects that are far away, too small or otherwise less significant in the quality of the final rendering. Many of these are used since the very
Hierarchical-Z map based occlusion culling
about 2 years ago - 26 comments
Hierarchical-Z is a well known and standard feature of modern GPUs that allows them to speed up depth testing by rejecting large group of incoming fragments using a reduced and compressed version of the depth buffer that resides in on-chip memory. The technique presented in this article uses the same basic idea to allow batched
OpenGL 4.0 – Mountains demo released
about 2 years ago - 18 comments
OpenGL 3.0 capable GPUs introduced a level of processing power and programming flexibility that isn’t comparable with any earlier generations. After that, OpenGL 4.0 and the hardware supporting it even further pushed the limits of what previously seemed to be impossible. Thanks to these features nowadays more and more possibilities are available for the graphics
Efficient Gaussian blur with linear sampling
about 2 years ago - 55 comments
Gaussian blur is an image space effect that is used to create a softly blurred version of the original image. This image then can be used by more sophisticated algorithms to produce effects like bloom, depth-of-field, heat haze or fuzzy glass. In this article I will present how to take advantage of the various properties
An introduction to OpenGL 4.1
about 2 years ago - 11 comments
The Khronos Group keeps the pace that they set themselves being able to deliver the latest specification of OpenGL less than half year after the revolutionary appearance of OpenGL 4. Abandoning the OpenGL 3.x line of the specification (at least for a while) the new update concentrates on Shader Model 5.0 class GPUs and extensions
Instance Cloud Reduction reloaded
about 2 years ago - 16 comments
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 3 years ago - 1 comment
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 3 years ago - 23 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
