6.15.3. Pointers To Functions: Callback Functions

Programmers can find the address of any named component in a C++ program and save the address in a pointer. They typically use pointers to functions when it is necessary to pass one function as an argument to another function. Function pointers implement a programming feature named callback functions. To help clarify the following discussion, we'll call the passed function the callback function or simply the callback, and we'll call the function that receives the callback the outer function.

Programmers divide callback functions into two categories. The outer function runs a synchronous callback as a part of its execution - callbacks run while the outer function is still running. Alternatively, the outer function saves the address of an asynchronous callback in a variable. The program runs the asynchronous callbacks later, possibly after being triggered by some event. Programmers generally use callbacks in conjunction with library functions and multi-threaded applications. We will explore a simple example of an asynchronous callback here and a synchronous callback in the arrays chapter (see the qsort example), but multi-threaded programs are beyond the scope of this text.

Creating and using a pointer to a function can be divided into four distinct steps:

  1. Define an appropriate callback function. This step's difficulty is variable, depending on the function's complexity, but is often surprisingly simple.
  2. Define the function-pointer variable. The bewildering syntax needed for this step makes it the most challenging.
  3. Get the address of the function. Oddly, this step is easy, as illustrated in the following example.
  4. Call the function through a pointer. Again, this step is easy.

Defining function-pointer variables (step 2)

Every function has a name, return type, and parameter list. The function's name is unimportant for this step because the pointer creates an alias or new name. However, the pointer must capture the function's return type and the type of each parameter (the parameter names are unimportant).

Our challenge is understanding the three pairs of parentheses representing two distinct operators. Two pairs represent the function call operator - one pair for the outer function and one for the callback. The third pair are grouping parentheses. Recall that we use an asterisk in a variable definition to indicate the variable is a pointer. The grouping parentheses are necessary because the asterisk has lower precedence than the function call operator (i.e., parentheses).

An example is the easiest way to illustrate the steps for creating and using function pointers. An example will also help us understand necessary parentheses.

Example: The atexit Library Function

The atexit function allows programmers to register at least 32 asynchronous callback functions (some implementations allow more). The program runs the callbacks when the program ends (either by calling the exit function or by returning from main). The program calls the functions in the reverse order of their registration - the first function registered is the last function called. One possible use for atexit is to register one or more "clean up" functions. For example, the program may create some temporary files that it should remove as a part of its termination.

The functions that atexit registers must have a return type of void and may not have any arguments. The atexit function imposes these restrictions, not the requirements of callback functions in general. There are no restrictions on the code placed in the bodies of these functions.

void func()
{
	. . .
}
int atexit( void (*f)() );
atexit(func);
(a)(b)(c)
Function pointers: the first three steps of using callback functions.
  1. Defining an appropriate callback function (step 1) (ellipses denote detail omitted for simplicity).
  2. The prototype for the atexit function. The highlighted code is the syntax necessary to define a pointer-to-a-function variable (step 2). That is, the whole purpose of the highlighted code is to define variable f and has the following meanings:
    void The function to which f points must have a void return type.
    (*f) The variable f is a pointer. The grouping parentheses, which force the * operator to run first, are necessary because the function call parentheses, also an operator, have higher precedence than * (see the operator summary table for the relative precedence of the C++ operators).
    () The trailing parentheses form an operator that calls a function. In this context, the () operator signifies that (*f) points to a function. The function call operator, (), has very high precedence, which necessitates the grouping parentheses in (*f). Only three operators have a higher precedence than ().
  3. Calling the atexit function and passing the address of the callback function. By itself, the name of a function is the address of the function's entry point. So, the name of a function is a pointer to the function, which satisfies step 3.
#include <iostream>
#include <stdlib.h>	// for the atexit prototype
using namespace std;

void test1()
{
	cout << "Test 1\n";
}

void test2()
{
	cout << "Test 2\n";
}

int main()
{
	atexit(test1);
	atexit(test2);
	cout << "main is done\n";

	return 0;
}
Example: atexit. The example program demonstrates the syntax for using the atexit function. When executed, the program displays the following output on the console:
main is done
Test 2
Test 1

Pointers and Function Calls

The program illustrated in Figure 2 demonstrates the first three steps of using a function pointer, which is sufficient when calling a library function with a function-pointer argument. However, the atexit "hides" the call to the callback function (step 4). Programmers can also write functions that, like atexit, require one or more function-pointer arguments. Fortunately, it's quite easy to call a function passed as an argument, as illustrated by the following example.

int my_callback(int x)			// (a, step 1)
{
	. . .
}

-----------------------------------

function(10, my_callback);		// (b, step 3)

-----------------------------------

void function(int a, int (*cb)(int))	// (c, step 2)
{
	cb(a);				// (d, step 4)
}
Function pointers and callback functions. Callback functions may have any valid data type as a return type and may have zero or more arguments of any valid type. The following example is deliberately different to reinforce these concepts.
  1. A callback function with an integer return type and parameter.
  2. An outer function requiring two arguments, defined in an application program. The second argument, highlighted in yellow, gets the address of my_caller and passes it to function.
  3. The highlighted syntax defines the variable cb as a pointer to a function. Furthermore, the function cb points to has an integer return type and argument.
  4. The function-call parentheses form an operator that acts or operates on an address. So, calling a function through a pointer parameter looks like any other function call but uses the parameter name.