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:
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.
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) |
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 (). |
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.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.