Click here to Skip to main content
Click here to Skip to main content
Go to top

Type-safe Callbacks in C++

, 13 Feb 2008
Rate this:
Please Sign up or sign in to vote.
Demonstrates how to use type-safe callbacks in C++

Features

This class adds type-safe C++ callbacks to your projects.

  • Any function in any class can be called from anywhere in any other class.
  • You can pass 0 to 5 arguments of any type to the callback function and specify any return type.
  • A callback can be passed as an argument to any function.
  • It is optimized for high speed.
  • The code size is less than 1 KB, no extra libraries required.
  • It is platform-independent: running on Windows, Linux, Mac, etc...
  • It has been tested on Visual Studio 6.0, 7.0, 7.1 and 8.0 (= Visual Studio 6, up to .NET 2005).
  • New in version 3.0 (Oct 2007): it also supports callbacks to static functions and to functions inside virtually derived classes.

Introduction

In C++, the simple address of a function is not enough to define a callback, as in old C. In C++, every instance of a class uses its own memory area for storing class variables. The this pointer points to this area of variables. With every call to any C++ function, the this pointer is passed invisibly to the function and additionally to the function's arguments. Microsoft Visual Studio 6 uses the processor register ECX to pass the this pointer while the normal function arguments are pushed on the stack. To use callbacks, simply copy the files Callback.h and PreProcessor.h to your project and #include "Callback.h".

Why Using Callbacks?

Let's say you wrote a scheduler that reacts on certain events by executing corresponding actions. The events may be anything like arriving data, user input or timers and the actions are execution of code of any kind. The scheduler contains an endless loop in an extra thread which does nothing else other than wait for events. Under Windows, you could use the API function WaitForMultipleObjects(), which returns the index of the event that was signaled. Such a scheduler could look like this:

Callback-Scheduler.gif

The sample code accompanying this article uses callbacks for sorting a list with different callback sort functions. Note that with signals and slots, you don't have to write your own RegisterCallback() function! See Part 2 of this article series for more details.

Defining Callbacks

There are two classes in callback.h: cCall and cCallGen. What happens inside these classes is complicated, so I will only explain the usage here. cCall is defined as:

cCall <_ReturnType, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5>

You can use callback functions taking 0, 1, 2, 3, 4 or 5 arguments. If you need more than 5 arguments, you can easily expand the callback classes on your own. However, I recommend passing more than 5 arguments in a structure instead, so the code becomes more readable.

Let's say the function that you want to call back is:

int cMyApplication::Calculate(float Factor, bool Flag, char* Name)
{
    ......
}

For that function, you can create a corresponding callback:

cCall <int, float, bool, char*> i_CallbackCalculate;

The first template argument (int) is always used to define the return type. The following template arguments (float, bool, char*) are the types of the arguments passed to the callback function.

To define a callback for use with the following void returning function without arguments...

void cMyApplication::Print()
{
    ......
}

...write:

cCall <void> i_CallbackPrint;

Assigning Callbacks

Callbacks can be assigned to each other by using operator=, but as these callbacks are type-safe, the following leads to a compiler error:

i_CallbackCalculate = i_CallbackPrint;  // error

To i_CallbackCalculate of the type int function(float, bool, char*), you cannot assign i_CallbackPrint of the type void function(void).

Generating Callbacks

i_CallbackCalculate and i_CallbackPrint could not be used up to now because they were not yet initialized, which means that no function has been assigned to them. If you call i_CallbackCalculate.Valid(), the result will be false, which means that you cannot use the callback. If you try to (i_CallbackCalculate.Execute(...)), the program will be aborted through an assert. To generate initialized callbacks for the above functions, you have to use cCallGen. cCallGen (the callback "Generator") inherits from cCall and is only used once for initialization. cCallGen is defined as cCallGen <_Class, _ReturnType, _Arg1, _Arg2, _Arg3, _Arg4, _Arg5>.

cCallGen <cMyApplication, int, float, bool, char*> i_CallGenCalculate(this, Calculate);
// and
cCallGen <cMyApplication, void> i_CallGenPrint(this, Print);

You see that cCallGen takes one template argument more than cCall. The first type (cMyApplication) is the class that contains the callback function; the rest is the same as for cCall. The parameters passed to the constructor are the this pointer of the class, which contains the callback function, and the callback function itself (Calculate, Print). Now you see why there are two callback classes. cCallGen can be used only inside the class which contains the callback function (here cMyApplication) because it needs the this pointer. cCall can be created in any class and later a compatible callback passed from anywhere can be be assigned to it:

i_CallbackCalculate = i_CallGenCalculate;
i_CallbackPrint     = i_CallGenPrint;

This assignment is possible because cCallGen inherits from cCall. Now you can execute the callback by writing:

int Result = i_CallbackCalculate.Execute(3.448, true, "Hello world");
// and
i_CallbackPrint.Execute();

If your callback functions are static, you have to use cCallGenS instead of cCallGen.

Usage Example

The following example demonstrates a list class that calls external functions to sort the data. By assigning different callback functions, you can sort the list by different criteria.

Callback-Scheme.gif

So this list becomes very flexible because the caller can manipulate the sorting behaviour without manipulating the code in cList. This is the spirit of C++. You could do the same by inheriting from cList and overwriting the Compare function. However, with callbacks you can change the list behaviour dynamically at runtime by simply calling SetCallback(...) as often as you like.

The Interface, Overview

cCall cCallGen (for member and virtual callbacks) cCallGenS (for static callbacks)
Constructor:
cCall <tRet [,tArg1,,,tArg5]> MyCall()
Constructor:
cCallGen <cClass, tRet [,tArg1,,,tArg5]> MyCallGen(Instance, Function)
Constructor:
cCallGenS <tRet [,tArg1,,,tArg5]> MyCallGen(Function)
cCall& operator=(cCall &ExtCall)
cCall& operator=(cCallGen &ExtCallGen)
- -
bool Valid() - -
tRet Execute([tArg1, tArg2, tArg3, tArg4, tArg5]) - -

To see how all the stuff works together, download the sample code! It is easy to understand.

Errors

You get compiler errors if you try to assign a callback to an incompatible callback (with different arguments or return type). You get runtime errors (assert) if you pass the wrong count of arguments to i_Callback.Execute(...) or if you try to execute an uninitialized callback.

Boost

I extracted Preprocessor.h from Boost::function. Boost is a huge platform-independent C++ library (35 MB) that uses a million tricks to make the compiler do things which normally are not possible. Because every compiler behaves differently and has other restrictions, Boost contains thousands of switches, #defines and typedefs to make it run on all compilers. There are lots of interesting things in Boost, but also disadvantages:

  • Boost is huge (35 MB).
  • It slows down the compilation process.
  • Sometimes it completely crashes my compiler, requiring a reboot.
  • It is nearly impossible to understand the extremely cryptic code in the 100 header files used for Boost::function.

My callback class is much more comfortable than Boost::function.

QT

Another library you should know about is QT. QT is a huge platform-independent GUI library. However, even if you don't want to program a GUI, QT is interesting because it offers platform-independent functions for file access, timers, etc. QT is also more: it circumvents restrictions in the compiler and even expands the C++ language itself by integrating an additional preprocessor into the compiler. This allows new functionality like easier multi-language support, runtime type information, guarded pointers, better readable code and signals and slots, which are advanced callbacks (see Part 2 of this article series). Unfortunately, QT also has disadvantages:

  • It costs $1500 (except for open source Linux development).
  • It is very huge (20 MB of sources).
  • It slows down the compilation process.
  • Programs written with QT have to deliver some additional DLLs of approximately 6 MB total size.
  • Signals and slots cannot be used to return a value to the caller.

So, if you don't want the "big ones" like Boost or QT, this tiny callback class by ElmueSoft is a "nice to have" that you will not want to miss if you once got used to it.

Part 2: Type-safe C++ Signals and Slots (Events and Delegates)

Click here to get to Part 2!

Read Part 2, which is based on this article and implements a very comfortable Signal / Slot (Event / Delegate) system in C++ without requiring any big library!

  • A signal (event) fired anywhere in any class of your code can be received by slots (delegates) in any class of your code.
  • You can connect as many signals to a slot and as many slots to a signal as you like.
  • You can pass 0 to 5 arguments of any type to a signal and you can combine the multiple return values from the slots to one single return value given back to the caller.
  • If a class is destroyed, all connected signals or slots are automatically disconnected.
  • Signals are reentrance-safe.
  • It is optimized for high speed.
  • The compiled code size is less than 1 KB, no extra libraries required.
  • It is much more comfortable than Boost::signals.
  • Some additional extra features are available.
  • It is platform-independent: running on Windows, Linux, Mac, etc...
  • It has been tested on Visual Studio 6.0, 7.0, 7.1 and 8.0 (= Visual Studio 6 up to .NET 2005)

P.S. From my homepage, you can download free C++ books in compiled HTML format.

History

  • 19 February, 2004 -- Original version posted
  • 21 April, 2004 -- First update
  • 13 February, 2008 -- Second update
    • Version 3.0
    • Article content and source download updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Elmue
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 27 years.

Comments and Discussions

 
QuestionMy Vote of 5 [modified] PinmemberMember 435807519-Jun-14 23:55 
AnswerRe: My Vote of 5 PinmemberElmue20-Jun-14 10:27 
GeneralRe: My Vote of 5 Pinmembermaxspot1-Jul-14 23:08 
GeneralRe: My Vote of 5 PinmemberElmue4-Jul-14 7:35 
GeneralMy vote of 5 PinmemberCloud Hsu30-Jan-12 23:14 
BugCompile on linux with g++ Pinmembervhucko10-Sep-11 10:57 
GeneralChange PinmemberComaWhite861-Apr-09 18:08 
NewsIMPORTANT: Download the new Version 3.0 !!! PinmemberElmue18-Oct-07 9:14 
Generalerror C3867 with VS .NET 2005 Pinmemberknopper24-May-07 2:18 
GeneralRe: The problem is solved !! PinmemberElmue24-May-07 4:02 
GeneralRe: error C3867 with VS .NET 2005 Pinmemberjeffb4212-Oct-07 12:04 
GeneralRe: The problem is solved !! PinmemberElmue18-Oct-07 9:29 
GeneralCE Compilation issues Pinmemberchuckb6220-Oct-06 7:46 
GeneralRe: The problem is solved !! PinmemberElmue24-Oct-06 8:15 
GeneralProblems with VC6.0 PinmemberJonathan Rosanowski12-Jan-05 9:47 
GeneralRe: Problems with VC6.0 [modified] PinmemberElmue16-Jan-05 21:57 
GeneralRe: Problems with VC6.0 PinmemberKevin Gutteridge4-Feb-05 7:18 
GeneralRe: Problems with VC6.0 PinmemberKevin Gutteridge5-Feb-05 5:14 
GeneralRe: Problems with VC6.0 PinmemberDon Clugston4-May-05 17:33 
GeneralRe: Problems with VC6.0 PinmemberKevin Gutteridge11-May-05 6:06 
GeneralRe: Problems with VC6.0 PinmemberDon Clugston11-May-05 14:48 
GeneralRe: The problem is solved !! PinmemberElmue18-Oct-07 9:31 
GeneralWARNING: There's a bug in the heart of the code in this article! PinmemberDon Clugston4-May-05 17:16 
GeneralRe: The problem is solved !! PinmemberElmue18-Oct-07 9:32 
QuestionCould the cCallGen object be global? PinmemberCris11-Nov-04 5:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140916.1 | Last Updated 13 Feb 2008
Article Copyright 2004 by Elmue
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid