Click here to Skip to main content
15,860,859 members
Articles / Programming Languages / C++
Article

Type-safe Callbacks in C++

Rate me:
Please Sign up or sign in to vote.
4.71/5 (47 votes)
13 Feb 2008CPOL8 min read 202.7K   4.9K   130   46
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:

C++
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:

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

For that function, you can create a corresponding callback:

C++
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...

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

...write:

C++
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:

C++
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>.

C++
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:

C++
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:

C++
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)


Written By
Software Developer (Senior) ElmüSoft
Chile Chile
Software Engineer since 40 years.

Comments and Discussions

 
Questionvs2019 compiling debug errors Pin
kuelite25-Jan-22 3:42
kuelite25-Jan-22 3:42 
QuestionMy Vote of 5 Pin
software@elitron.com19-Jun-14 23:55
software@elitron.com19-Jun-14 23:55 
AnswerRe: My Vote of 5 Pin
Elmue20-Jun-14 10:27
Elmue20-Jun-14 10:27 
GeneralRe: My Vote of 5 Pin
maxspot1-Jul-14 23:08
maxspot1-Jul-14 23:08 
GeneralRe: My Vote of 5 Pin
Elmue4-Jul-14 7:35
Elmue4-Jul-14 7:35 
GeneralMy vote of 5 Pin
Cloud Hsu30-Jan-12 23:14
Cloud Hsu30-Jan-12 23:14 
BugCompile on linux with g++ Pin
vhucko10-Sep-11 10:57
vhucko10-Sep-11 10:57 
GeneralChange Pin
ComaWhite861-Apr-09 18:08
ComaWhite861-Apr-09 18:08 
NewsIMPORTANT: Download the new Version 3.0 !!! Pin
Elmue18-Oct-07 9:14
Elmue18-Oct-07 9:14 
Generalerror C3867 with VS .NET 2005 Pin
knopper24-May-07 2:18
knopper24-May-07 2:18 
GeneralRe: The problem is solved !! Pin
Elmue24-May-07 4:02
Elmue24-May-07 4:02 
GeneralRe: error C3867 with VS .NET 2005 Pin
jeffb4212-Oct-07 12:04
jeffb4212-Oct-07 12:04 
GeneralRe: The problem is solved !! Pin
Elmue18-Oct-07 9:29
Elmue18-Oct-07 9:29 
GeneralCE Compilation issues Pin
chuckb6220-Oct-06 7:46
chuckb6220-Oct-06 7:46 
GeneralRe: The problem is solved !! Pin
Elmue24-Oct-06 8:15
Elmue24-Oct-06 8:15 
GeneralProblems with VC6.0 Pin
Jonathan Rosanowski12-Jan-05 9:47
Jonathan Rosanowski12-Jan-05 9:47 
GeneralRe: Problems with VC6.0 [modified] Pin
Elmue16-Jan-05 21:57
Elmue16-Jan-05 21:57 
GeneralRe: Problems with VC6.0 Pin
Kevin Gutteridge4-Feb-05 7:18
Kevin Gutteridge4-Feb-05 7:18 
GeneralRe: Problems with VC6.0 Pin
Kevin Gutteridge5-Feb-05 5:14
Kevin Gutteridge5-Feb-05 5:14 
GeneralRe: Problems with VC6.0 Pin
Don Clugston4-May-05 17:33
Don Clugston4-May-05 17:33 
GeneralRe: Problems with VC6.0 Pin
Kevin Gutteridge11-May-05 6:06
Kevin Gutteridge11-May-05 6:06 
GeneralRe: Problems with VC6.0 Pin
Don Clugston11-May-05 14:48
Don Clugston11-May-05 14:48 
GeneralRe: The problem is solved !! Pin
Elmue18-Oct-07 9:31
Elmue18-Oct-07 9:31 
GeneralWARNING: There's a bug in the heart of the code in this article! Pin
Don Clugston4-May-05 17:16
Don Clugston4-May-05 17:16 
GeneralRe: The problem is solved !! Pin
Elmue18-Oct-07 9:32
Elmue18-Oct-07 9:32 

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

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