[Home]Generalizing C-Style Callbacks

BOOST WIKI | RecentChanges | Preferences | Page List | Links List

Generalizing C-style Callbacks with Boost.Function

This article describes how to use Boost.Function to make a C-style callback (implemented with a function pointer and a user data pointer) into a callback that can handle arbitrary function objects or functions.

Introduction

Callbacks are generally implemented in the C language using function pointers and auxiliary user-defined data passed as a void pointer for genericity. For simple algorithms in C++, such as those in the C++ standard library, templates have allowed designers to lift the restrictions inherent in a function pointer-based implementation. For instance, compare the C library function qsort with the C++ standard library function sort: qsort works only on arrays with simple elements, whereas sort can work on any sequence accessible via random access iterators of essentially any type of data, with a user-defined ordering function.

Unfortunately, in nontrivial situations, callbacks are not often generalized from the C-style function pointer because of a lack of language support. Though libraries exist to generalize the notion of a callback for C++ (Boost.Function is one such library), function pointers are still used in many cases because libraries did not exist at the original design time.

Given a C-style callback consisting of a function pointer and some form of user data, we can use a small piece of glue code to generalize the use of that callback to allow arbitrary function objects. This enables the callback to be used in more contexts more easily, but without requiring massive changes to the code that invokes the callback.

Generalizing C-style visitors

C-style visitors are the simplest class of C-style callbacks to convert to a more general C++ style. The following is the declaration of the walk_tree function in the source code to the GNU C++ compiler:

 typedef tree (*walk_tree_fn)(tree *, int *, void *);
 extern tree walk_tree(tree *t, walk_tree_fn fn, void *data);

As is common for many implementations of the visitor pattern in C, the void pointer data provided to walk_tree is passed through to the visitor fn unchanged; the first two parameters to the visitor are the abstract syntax tree element we are visiting and an integer that is nonzero if subtrees should be visited. We can use this parameter to store information about a Boost.Function object in our C++-style walkTree function below:

 typedef boost::function<tree, tree&, bool&> WalkTreeFunction;

 tree walk_visitor_helper(tree* t, int* walk_subtrees, void* fp)
 {
   bool ws = *walk_subtrees;
   WalkTreeFunction* f = static_cast<WalkTreeFunction*>(fp);
   tree result = (*f)(*t, ws);
   *walk_subtrees = ws? 1 : 0;
   return result;
 }

 tree walkTree(tree& t, WalkTreeFunction f)
 {
   return walk_tree(&t, &walk_visitor_helper, &f);
 }

In this example, we define the type of our visitor to be WalkTreeFunction, which represents a C++-style view of the actual operation, and can be implicitly constructed from any function object with a compatible function signature. The walkTree function itself (that is accessed by users) is merely a forwarding function to call the original walk_tree C-style function. The walk_visitor_helper function performs the majority of the work: it adapts the C++-style interface based on Boost.Function to the old C-s tyle interface used by walk_tree, including conversions from C types to the more descriptive C++ types (e.g., using bool instead of int for boolean values, and replacing pointers with references).

This scheme does incur a small bit of extra overhead because there are at least two calls though function points: walk_tree or its children will call our walk_visitor_helper, which will in turn perform a call through the Boost.Function object.

A Common Callback Scheme

The following code illustrates a simple class that has a single callback that is trigged when, for instance, a mouse button is pressed. The data for the callback consists of the mouse pointer location and a value determining which mouse button was pressed; additional data specified by the user is passed as a void pointer so that the user may encode any information in it.

 enum mouse_button { mb_left, mb_middle, mb_right };
 typedef void (*click_callback_type)(mouse_button, int, int, void*);

 class widget {
 public:
   void set_click_callback(click_callback_type f, void* data)
   {
     m_click_callback = f;
     m_click_data = data;
   }

 private:
   click_callback_type m_click_callback;
   void*                    m_click_data;
 };

To use the click callback, one must create a function with the exact function signature click_callback_type that decodes the void pointer into some meaningful data that was initially set in the call to set_click_callback. The dangers of such a system are obvious: all type information is lost when the data is passed via a void pointer, so it becomes very easy for the programmer to lose track of the original type of the data or the memory allocated to store the data.

Some changes to the widget class afford greater flexibility and safety, without losing backward compatibility. The following code highlights the necessary changes in boldface:

 enum mouse_button { mb_left, mb_middle, mb_right };
 typedef void (*click_callback_type)(mouse_button, int, int, void*);

 class widget {
   typedef boost::function3<void, mouse_button, int, int> generic_click_callback;

   static void click_thunk(mouse_button b, int x, int y, void* self)
   {
     static_cast<widget*>(self)->m_click_generic_callback(b, x, y);
   }

 public:
   template<typename F>
   void set_click_callback(F f)
   {
     m_click_callback = click_thunk;
     m_click_data     = static_cast<void*>(this);
     m_click_generic_callback = f;
   }

   void set_click_callback(click_callback_type f, void* data)
   {
     m_click_callback = f;
     m_click_data = data;
     m_click_generic_callback.clear();
   }

 private:
   click_callback_type m_click_callback;
   void*               m_click_data;
   generic_click_callback m_click_generic_callback;
 };

The idea behind the above code is that we add a new member, m_click_generic_callback, that can call any function or function object that can accept a mouse_button argument followed by two int arguments. The click_thunk routine acts as a bridge between the C-style callback and the new version based on Boost.Function. This new code retains backward compatibility by retaining the original function pointer/void pointer interface, but enables a more flexible interface; the difference is analagous to the move from the C library's qsort routine to the C++ library sort routine.

An Alternative Implementation using Boost.Bind

The recently introduced Boost.Bind library offers yet another solution to this problem, though this solution requires changing the calls to the callback itself to remove the void user data pointer. The updated code follows:

enum mouse_button { mb_left, mb_middle, mb_right }; typedef void (*click_callback_type)(mouse_button, int, int, void*);

 class widget {
   typedef boost::function3<void, mouse_button, int, int> generic_click_callback;

 public:
   template<typename F>
   void set_click_callback(F f)
   {
     m_click_callback = f;
   }

   void set_click_callback(click_callback_type f, void* data)
   {
     m_click_callback = boost::bind(f, _1, _2, _3, data);
   }

 private:
   generic_click_callback m_click_callback;
 };

Here, we completely abandon the C-style callback and use Boost.Function directly. The backwards-compatible version of set_click_callback uses the binding facilities of Boost.Bind to send the pointer to the old C-style callback without requiring it to be explicitly stored in a widget object. The call to boost::bind creates a function object taking three arguments, which will be passed to the function f in their original order, but also a fourth parameter, the user data pointer data, will be passed to f. This makes the old C-style callback look like a new-style callback to the rest of the widget class.

Conclusion

Boost.Function offers the ability to generalize the notion of a callback to be more responsive to programmer needs by giving the flexibility to all routines that are generally only available to algorithms that do not store the callback itself (e.g., sort or for_each). Boost.Function (perhaps with Boost.Bind) also allows a migration path from C-style callbacks to C++-style callbacks that is backwards-compatible and trivial to implement.


BOOST WIKI | RecentChanges | Preferences | Page List | Links List
Edit text of this page | View other revisions
Last edited September 6, 2004 9:13 am (diff)
Search:
Disclaimer: This site not officially maintained by Boost Developers