A callback function is a function, which is an argument, not a parameter, in another function. The other function can be called the principal function. So two functions are involved: the principal function and the callback function itself. In the parameter list of the principal function, the declaration of the callback function without its definition is present, just as object declarations without assignment are present. The principal function is called with arguments (in main()). One of the arguments in the principal function call is the effective definition of the callback function. In C++, this argument is a reference to the definition of the callback function; it is not the actual definition. The callback function itself is actually called within the definition of the principal function.
The basic callback function in C++ does not guarantee asynchronous behavior in a program. Asynchronous behavior is the real benefit of the callback function scheme. In the asynchronous callback function scheme, the result of the principal function should be obtained for the program before the result of the callback function is obtained. It is possible to do this in C++; however, C++ has a library called future to guarantee the behavior of the asynchronous callback function scheme.
This article explains the basic callback function scheme. A lot of it is with pure C++. As far as the callback is concerned, the basic behavior of the future library is also explained. Basic knowledge of C++ and its pointers is necessary for the understanding of this article.
Article Content
- Basic Callback Function Scheme
- Synchronous Behavior with Callback Function
- Asynchronous Behavior with Callback Function
- Basic use of the future Library
- Conclusion
Basic Callback Function Scheme
A callback function scheme needs a principal function, and the callback function itself. The declaration of the callback function is part of the parameter list of the principal function. The definition of the callback function is indicated in the function call of the principal function. The callback function is actually called within the definition of the principal function. The following program illustrates this:
using namespace std;
int principalFn(char ch[], int (*ptr)(int))
{
int id1 = 1;
int id2 = 2;
int idr = (*ptr)(id2);
cout<<"principal function: "<<
id1<<' '<<ch<<' '<<idr<<'\n';
return id1;
}
int cb(int iden)
{
cout<<"callback function"<<'\n';
return iden;
}
int main()
{
int (*ptr)(int) = &cb;
char cha[] = "and";
principalFn(cha, cb);
return 0;
}
The output is:
principal function: 1 and 2
The principal function is identified by principalFn(). The callback function is identified by cb(). The callback function is defined outside the principal function but actually called within the principal function.
Note the declaration of the callback function as a parameter in the parameter list of the principal function declaration. The declaration of the callback function is “int (*ptr)(int)”. Note the callback function expression, like a function call, in the definition of the principal function; any argument for the callback function call is passed there. The statement for this function call is:
Where id2 is an argument. ptr is part of the parameter, a pointer, that will be linked to the reference of the callback function in the main() function.
Note the expression:
In the main() function, which links the declaration (without definition) of the callback function to the name of the definition of the same callback function.
The principal function is called, in the main() function, as:
Where cha is a string and cb is the name of the callback function without any of its argument.
Synchronous Behavior of Callback Function
Consider the following program:
using namespace std;
void principalFn(void (*ptr)())
{
cout<<"principal function"<<'\n';
(*ptr)();
}
void cb()
{
cout<<"callback function"<<'\n';
}
void fn()
{
cout<<"seen"<<'\n';
}
int main()
{
void (*ptr)() = &cb;
principalFn(cb);
fn();
return 0;
}
The output is:
callback function
seen
There is a new function here. All the new function does, is to display the output, “seen”. In the main() function, the principal function is called, then the new function, fn() is called. The output shows that the code for the principal function was executed, then that for the callback function was executed, and finally that for the fn() function was executed. This is synchronous (single-threaded) behavior.
If it were asynchronous behavior, when three code segments are called in order, the first code segment may be executed, followed instead by the execution of the third code segment, before the second code segment is executed.
Well, the function, fn() can be called from within the definition of the principal function, instead of from within the main() function, as follows:
using namespace std;
void fn()
{
cout<<"seen"<<'\n';
}
void principalFn(void (*ptr)())
{
cout<<"principal function"<<'\n';
fn();
(*ptr)();
}
void cb()
{
cout<<"callback function"<<'\n';
}
int main()
{
void (*ptr)() = &cb;
principalFn(cb);
return 0;
}
The output is:
seen
callback function
This is an imitation of asynchronous behavior. It is not asynchronous behavior. It is still synchronous behavior.
Also, the order of execution of the code segment of the principal function and the code segment of the callback function can be swapped in the definition of the principal function. The following program illustrates this:
using namespace std;
void principalFn(void (*ptr)())
{
(*ptr)();
cout<<"principal function"<<'\n';
}
void cb()
{
cout<<"callback function"<<'\n';
}
void fn()
{
cout<<"seen"<<'\n';
}
int main()
{
void (*ptr)() = &cb;
principalFn(cb);
fn();
return 0;
}
The output is now,
principal function
seen
This is also an imitation of asynchronous behavior. It is not asynchronous behavior. It is still synchronous behavior. True asynchronous behavior can be obtained as explained in the next section or with the library, future.
Asynchronous Behavior with Callback Function
The pseudo-code for the basic asynchronous callback function scheme is:
type cb(type output)
{
//statements
}
type principalFn(type input, type cb(type output))
{
//statements
}
Note the positions of the input and output data in the different places of the pseudo-code. The input of the callback function is its output. The parameters of the principal function are the input parameter for the general code and the parameter for the callback function. With this scheme, a third function can be executed (called) in the main() function before the output of the callback function is read (still in the main() function). The following code illustrates this:
using namespace std;
char *output;
void cb(char out[])
{
output = out;
}
void principalFn(char input[], void (*ptr)(char[50]))
{
(*ptr)(input);
cout<<"principal function"<<'\n';
}
void fn()
{
cout<<"seen"<<'\n';
}
int main()
{
char input[] = "callback function";
void (*ptr)(char[]) = &cb;
principalFn(input, cb);
fn();
cout<<output<<'\n';
return 0;
}
The program output is:
seen
callback function
In this particular code, the output and input datum happens to be the same datum. The result of the third function call in the main() function has been displayed before the result of the callback function. The callback function executed, finished, and assigned its result (value) to the variable, output, allowing the program to continue without its interference. In the main() function, the output of the callback function was used (read and displayed) when it was needed, leading to asynchronous behavior for the whole scheme.
This is the single-threaded way to obtain callback function asynchronous behavior with pure C++.
Basic use of the future Library
The idea of the asynchronous callback function scheme is that the principal function returns before the callback function returns. This was done indirectly, effectively, in the above code.
Note from the above code that the callback function receives the main input for the code and produces the main output for the code. The C++ library, future, has a function called sync(). The first argument to this function is the callback function reference; the second argument is the input to the callback function. The sync() function returns without waiting for the execution of the callback function to complete but allows the callback function to complete. This provides asynchronous behavior. While the callback function continues to execute, since the sync() function has already returned, the statements below it continue to execute. This is like ideal asynchronous behavior.
The above program has been rewritten below, taking into consideration, the future library and its sync() function:
#include <future>
#include <string>
using namespace std;
future<string> output;
string cb(string stri)
{
return stri;
}
void principalFn(string input)
{
output = async(cb, input);
cout<<"principal function"<<'\n';
}
void fn()
{
cout<<"seen"<<'\n';
}
int main()
{
string input = string("callback function");
principalFn(input);
fn();
string ret = output.get(); //waits for callback to return if necessary
cout<<ret<<'\n';
return 0;
}
The sync() function finally stores the output of the callback function into the future object. The expected output can be obtained in the main() function, using the get() member function of the future object.
Conclusion
A callback function is a function, which is an argument, not a parameter, in another function. A callback function scheme needs a principal function, and the callback function itself. The declaration of the callback function is part of the parameter list of the principal function. The definition of the callback function is indicated in the function call of the principal function (in main()). The callback function is actually called within the definition of the principal function.
A callback function scheme is not necessarily asynchronous. To be sure that the callback function scheme is asynchronous, make the main input to the code, the input to the callback function; make the main output of the code, the output of the callback function; store the output of the callback function in a variable or data structure. In the main() function, after calling the principal function, execute other statements of the application. When the output of the callback function is needed, in the main() function, use (read and display) it there and then.