- Community Pick
- Experts Exchange Approved
In C, if you want to provide a callback mechanism you must implement a callback function and then pass the address of the callback function to the invoker (the code that will call your callback when it's needed). Unfortunately, C style function pointers have a number of drawbacks:
1. A function contains no instance state, mainly because there is no such thing as multiple instances of a function; there will only ever be one instance and that is global. Sure, it is possible to declare a local static within the function that can retain state between calls, but since a function only has one instance there can only be once instance of the static member and that must be shared between function calls.
A function with local static state
2. If you try to maintain state in the function by using a local static variable the function will not be reentrent, so it cannot be safely called on multiple threads without the additional overhead of thread synchronisation to ensure access to the local static data has mutual exclusion semantics. This effectively means the function can only allow one thread into it at any one time and that will create quite a bottle neck of thread contention (multiple threads all fighting for access to a single resource). Furthermore, if access is required to the static state local variable after the function has finished the caller must continue to block access until the state has either been copied for use or it is no longer required otherwise it'll be read by one thread whilst another is potentially trying to modify it, resulting in a race condition.
A function with local static state using a mutex to attempt to make the function reentrant
3. Function pointers do not work very well with templates, if only one signature of the function exists then things will work otherwise the compiler will complain of template instantiation ambiguities that can only be resolved through ugly function pointer casting.
A function pointer casting to resolve template instantiation ambiguities
4. What if your invoker expects a function that takes only one parameter and you want to use a 3rd party function as the callback, to which you have no source code, and this has a completely different signature to that expected by the invoker? Well, you can wrap the function with another function that adapts the interface but this wrapper will need to have the additional required parameters hard coded into it. What if you want the flexibility of changing what the parameter values are for arbitrary calls to the invoker? Well, for that you will need to write an adaptor for each and every permutation and even then it's still fixed at compile time so it's not easily extensible and becomes quite messy with multiple functions that need to be maintain.
An adaptor using a function
So, is there a better way? Well, now you come to mention it, yes there is! Enter the functor.
What is a functor? Well, simply put it is a function object, and in C-PlusPlus we model a functor using a normal class object. What makes the object a functor is the provision of a function operator, which gives the class object function semantics. The function operator, in its simply canonical for looks like this...
The basic canonical form of a functor
Where R is the return type, T is a parameter type and just like any function the number of parameters is arbitrary. So a more concrete type of a functor that takes 2 int parameters and returns a bool would be as follows...
A concrete example of a simple functor
It's pretty clear to see that this simple functor will compare two integers and if the left-hand-side is less than the right-hand-side it will return true, else it will return false.
How do we use a functor and how does it differ from a function? Well, as already stated a functor is just a class with function semantics, of course it is still just a class and like all classes it can contain data and function members and instances can be created. What this means is that each instance of the functor object can contain and maintain its own internal state. This state can either be set during construction of the function or after construction. This means the functor can be primed with state before use and it can set its own state during use, which can be extracted after.
Lets look at a functor in action. First of all, let's revisit the issue of binding parameters to 3rd party functions to facilitate using them in with an invoker that expects a different signature. With a functor the additional parameters can be loaded into the functor instance when it is created, which can then be bound to the third party call at run-time and not compile time. In fact this is exactly what the standard library functions bind1st and bind2nd do. Look at the example below, notice how much neater the solution is, no need for multiple functions to provide multiple bindings and also note that the bindings provided are passed into the constructor of the functor rather than being hard-coded into it, thus allowing these bindings to be changed at run-time.
An adaptor using a functor
How about another example? A common usage of a callback function is to provide a user defined logging mechanism for a 3rd party library. The library will callback to your logging callback function, provide it with some details and it is up to you to design a function that will do something useful with those details, like write them to a log file. Furthermore we must record how many times we logged something and how many of these were errors so at the end of the call to the third-party library we can append a count of entries to the log file. Implementing this using a standard C function callback mechanism would require quite a lot of effort, however, using a functor it's pretty simple.
A logging functor in action
Okay, we've had a couple of concrete example of using functors but I can hear you screaming, "are there any drawbacks?". Well, yes. For a start functors are not C API friendly and cannot really be made to work with an existing function that already expects an old C style function pointer. Other drawbacks? Well, unlike functions, functors have to be instantiated and like any object a functor can throw on construction so additional consideration must be given to ensure code is exception safe. Other than these few issues the use of a C-PlusPlus functor has few downsides and many benefits.
So how do you start making use of functors? When writing C-PlusPlus code that uses callbacks it is always a good idea to implement support for functors as well as function pointers. This is exactly what the Standard Template Library does. The code to support both functors and function pointers is quite simple, requiring only the use of a simple template parameter rather than an explicit function pointer type. Since the calling semantics of a functor and a function are identical, the invoker works just as well with either. In template meta-programming parlance we say that a functor models a function concept and, therefore, either a function or a functor can be passed as a template parameter where that parameter represents a function concept. Let's go back to the LessThan functor to see this.
How to write an invoker to use either a functor or a function
You'll find the functor concept is used ubiquitously by the C-PlusPlus STL (Standard Template Library) as well as the Boost libraries. Knowing how to write and use functors is a key success factor in writing generic and reusable code and being able to make use of advanced features of the STL and Boost. They are a tool that should be in any C-PlusPlus programmers toolkit!
Further reading: The Function Pointer Tutorials - Introduction to the basics of C and C-PlusPlus Function Pointers, Callbacks and Functors.
by: mwvisa1 on 2010-08-11 at 19:42:07ID: 18068