6.15.3. Pointers To Functions: Callback Functions

Review

Programmers can find the address of any named component in a C++ program, including functions, and save the address in a pointer variable. This section demonstrates function pointers with a programming technique called callback functions. In this context, programs pass one function as an argument to another, suggesting that the technique involves three functions: the function passing the function pointer, the function receiving the pointer, and the function referenced by the pointer. To clarify the following discussion, we'll call the passing or sending function the client, the passed or referenced function the callback function or simply the callback, and the receiving function the server.

Programmers divide callback functions into two categories. We explore a simple example of an asynchronous callback here, and a synchronous callback in the strings chapter (qsort and bsearch). A function's name represents its address or entry point (the address of the first machine instruction executed when a program calls the function). The following pseudo code illustrates the relationship between the three functions:

void callback() { ... }
	. . .
client() { ...; server(callback); ... }

In the case of an asynchronous callback, the client registers or "remembers" the callback's address, allowing it to call the function later, possibly after being triggered by some event. The most challenging aspect of working with function pointers is understanding the syntax necessary for defining appropriate pointer variables.

Defining Function-Pointer Variables

In contrast to simple pointers, defining function pointers involves two pairs of parentheses. Although the characters are the same for both pairs, the operators they represent are not. Understanding the purpose of the different operators is crucial for understanding the definition syntax.

Prerequisite ConceptsFunction Pointer Variable Definition
  • C++ syntax uses asterisks to define pointer variables: char* name;
  • Function prototypes consist of the function's name, its return type, and a parentheses-enclosed parameter list
  • A function's name is its address
  • C++ implements function calls with the function call operator that operates on an address: function();
return-type (*function)(parameter-list);
Understanding function pointers. When defining a function-pointer variable, the return type and parameter list parts of the definition must match the original, referenced function's signature. The C++ syntax continues using the asterisk to define a pointer variable, but its precedence is lower than the function call operator (highlighted in red). Consequently, the definition must surround the asterisk and function name with grouping parentheses (highlighted with green) to alter the evaluation sequence.

A Callback Example: The atexit Library Function

In the context of callback functions, the server function is responsible for defining the pointer variable parameter. In this example, the atexit library function fulfills this task, making it surprisingly easy to use. 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 terminates, 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 part of its termination.

atexit restricts the registered functions, requiring a void return type and disallowing any parameters, but does not restrict the code in the function bodies. Other server functions may have different requirements.

The callback function The server and its parameter Calling the server
void func()
{
	. . .
}
atexit(func);
int atexit( void (*f)() );
(a)(b)(c)
Function pointers: Client-side operations using callback functions. It's relatively easy for application or client programs to use atexit and similar library server functions. Applications are responsible for defining and registering the callback functions ((a) and (b)), but the atexit server hides the troublesome parameter definition (c).
  1. Defining an appropriate callback function (ellipses denote extraneous detail omitted for simplicity).
  2. Calling the atexit function and passing the address of the callback function.
  3. The prototype for the atexit function. The highlighted code defines a function-pointer parameter - it defines variable f.

 

#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 previous examples illustrate the general client-side operations using a library server function, and demonstrate them with the atexit function. However, they fail to demonstrate the syntax the server uses to call the referenced or passed callback function. Programmers can also write application-specific server functions that, like atexit, require one or more function-pointer arguments. Fortunately, calling a passed function is unexpectedly simple, as illustrated by the following pseudo code.

int my_callback(int x)
{
    . . .
}
function(10, my_callback);
void function(int a, int (*cb)(int))
{
    cb(a);
}
(a)(b)(c) and (d)
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. This example is deliberately different from the atexit examples to reinforce these concepts.
  1. An application-defined callback function with an integer return type and parameter.
  2. The call to an application-defined server function requiring two arguments. The second argument, highlighted in yellow, receives the address of the my_caller function. Depending on the server's synchronicity, it either saves the callback's address for use later or uses it as part of its current execution.
  3. The highlighted syntax defines the parameter variable, cb, as a pointer to a function returning an integer and requiring an integer 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. In this example, the statement highlighted with pink calls my_callback using the parameter cb.