Click here to Skip to main content
15,887,267 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 203.4K   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 
Jonathan Rosanowski wrote:
I tried this in a VC6.0 project and it worked until I added it to several places in a project. It stopped working because the compiler decided the tFunction members of kRetFunc should be 8 bytes while the tFunction members of kData should be 4 bytes, so the memcpy in the cCallGen constructor was copying structures of different sizes. I wasn't able to narrow down the problem. Any thoughts as to why this might happen?

Inside this code is a really nasty hack. The code casts member function pointers of one class to another unrelated class (cCall), by using a memcopy() from one struct to another. Then it uses the new class. This is undefined behaviour. It's undefined for a good reason. Implicitly, it assumes that all member function pointers are the same size -- but that's not true!
On all versions of VC, Intel, and CodePlay compilers, and some Borland compilers, the size of an MFP pointing to a class which uses multiple or virtual inheritance is greater than for a class which only uses single inheritance.
Since most programmers rarely use multiple inheritance, you could use this code for years without finding the problem. But there is a really nasty undefined behaviour lurking in the heart of this code.

It's a really nice idea, though, and it can be made to work.
For a full discussion, see my article at
www.codeproject.com/cpp/fastdelegate.cpp
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.