In C, the most common uses of callbacks are as parameters to library functions like
For example you might have a library that provides some sorting functions but you want to allow the library user to provide his own sorting function.
Since the arguments and the return values do not change depending on the sorting algorithm, this can be facilitated in a convenient manner using function callbacks.
Callbacks are also used as event listeners. onMouseClick(), onTerminalInput(), onData(), onConnectionStatus(), onRead() are probably some examples you've already seen in different libraries.
The libraries have these callback functions and the users of the library are supposed to implement them. The library has a function pointer to these functions and calls them on their event loop which will invoke the code of the inherited classes of the library user.
The implementation of function pointers is simple: they are just "code pointers": they hold the starting address of an assembly-language routine. The different types of function pointers exist only to ensure that the correct calling convention is used.
The callback implementation is traditionally done using C function pointers.
This however creates syntax-wise hard to tolerate code and might lead to bugs when virtual methods and templates are involved.
The syntax is often not portable leading to further issues.
boost::bind and boost::function can be used to achieve the same in a much more convenient and an intuitive manner. It's also faster. Note however that mechanisms other than function callbacks can be used almost every time to circumvent the use of function pointers, e.g. virtual methods.
However, function pointers are still important to learn but boost approach is usually recommended.
The following example of using function pointers as callbacks is self explanatory. One thing to note is that you cannot use function pointers with static member functions, i.e. you can't use a member function to point to a static member function. Things get much uglier when we consider templates and derived classes, so I'll stay away from them.
The Bind can actually be fairly efficient, as it can operate without ever going to the heap (Say what? "function pointers" should refer to some heap location right?). It simply copies your variables and placeholders to an internal structure on the stack, and returns that.
boost::bind can actually return a boost::function but the return structure can be quite complex.
By comparing the bind and non-bind versions it's immediately apparent the additional workload involved in using raw function pointers. You cannot have a boost::function that can take in arbitrary number/types of arguments resulting in defining multiple typedefs.
The object has to be associated with the callback - we pass the object by reference to class B.
The callback invokes are difficult in syntax.
So boot::function/boost::bind can alleviate all these issues.
1. typedef boost::function<void() > callback; can take arbitrary argument types arbitrary number of arguments - the example uses a std::string version and a std::string, int version. Note that the return type is always void.
2. By b.set_callback(boost::bind(&A::print, &a, s)); you are doing some really complex things that happen under the hood - all possible because of the smart guys who developed boost. We'll segregate the above into two steps.
boost::function cb = boost::bind(&A::print, &a, s);
// callback cb = boost::bind(&A::print, &a, s);
b.set_callback(cb);
What is essential happening is that object 'a' is bound to the boost::function returned by the boost (cb). When you call cb(), in the do_callback() what happens is something similar to a->print().
OK, what about the arguments?
Yeah, that's right. It gets bound along with the object. The boost bind returns a boost function which has all the information about the function pointer, object and the parameters.
This leads to another interesting topic called placeholders. Have a look;
What we are saying by _1 is that we are not supplying the parameter right now but we'll provide it later during the function invoking phase. See?
It's worthwhile to consider the usage of Observer pattern if one needs to think about using callbacks. It lacks flexibility in the number of arguments provided in the update() method and the pattern is usually abused if you do this but nevertheless it's a good choice.
qsort
,
and as callbacks for Windows functions, etc.For example you might have a library that provides some sorting functions but you want to allow the library user to provide his own sorting function.
Since the arguments and the return values do not change depending on the sorting algorithm, this can be facilitated in a convenient manner using function callbacks.
Callbacks are also used as event listeners. onMouseClick(), onTerminalInput(), onData(), onConnectionStatus(), onRead() are probably some examples you've already seen in different libraries.
The libraries have these callback functions and the users of the library are supposed to implement them. The library has a function pointer to these functions and calls them on their event loop which will invoke the code of the inherited classes of the library user.
The implementation of function pointers is simple: they are just "code pointers": they hold the starting address of an assembly-language routine. The different types of function pointers exist only to ensure that the correct calling convention is used.
The callback implementation is traditionally done using C function pointers.
This however creates syntax-wise hard to tolerate code and might lead to bugs when virtual methods and templates are involved.
The syntax is often not portable leading to further issues.
boost::bind and boost::function can be used to achieve the same in a much more convenient and an intuitive manner. It's also faster. Note however that mechanisms other than function callbacks can be used almost every time to circumvent the use of function pointers, e.g. virtual methods.
However, function pointers are still important to learn but boost approach is usually recommended.
The following example of using function pointers as callbacks is self explanatory. One thing to note is that you cannot use function pointers with static member functions, i.e. you can't use a member function to point to a static member function. Things get much uglier when we consider templates and derived classes, so I'll stay away from them.
The object has to be associated with the callback - we pass the object by reference to class B.
The callback invokes are difficult in syntax.
So boot::function/boost::bind can alleviate all these issues.
1. typedef boost::function<void() > callback; can take arbitrary argument types arbitrary number of arguments - the example uses a std::string version and a std::string, int version. Note that the return type is always void.
2. By b.set_callback(boost::bind(&A::print, &a, s)); you are doing some really complex things that happen under the hood - all possible because of the smart guys who developed boost. We'll segregate the above into two steps.
boost::function
// callback cb = boost::bind(&A::print, &a, s);
b.set_callback(cb);
What is essential happening is that object 'a' is bound to the boost::function returned by the boost (cb). When you call cb(), in the do_callback() what happens is something similar to a->print().
OK, what about the arguments?
Yeah, that's right. It gets bound along with the object. The boost bind returns a boost function which has all the information about the function pointer, object and the parameters.
This leads to another interesting topic called placeholders. Have a look;
class FooClass {
public:
void Print( int a ) {
std::cout << "A FooClass, param = "<< a <<" this = " << this << std::endl;
}
};
void main() {
FooClass *myFoo = new FooClass();
void( FooClass::* oldFunc )(int) = &FooClass::Print; //C style function pointer
(myFoo->*oldFunc)( 5 );
boost::function newFunc = boost::bind( &FooClass::Print, myFoo, _1 ); //boost function
newFunc( 5 );
}
What we are saying by _1 is that we are not supplying the parameter right now but we'll provide it later during the function invoking phase. See?
It's worthwhile to consider the usage of Observer pattern if one needs to think about using callbacks. It lacks flexibility in the number of arguments provided in the update() method and the pattern is usually abused if you do this but nevertheless it's a good choice.
Comments
Post a Comment