There are various ways to implement callback methods. Basically, the traditional way is to use function pointers or function references. However, C++11 introduces a notion of callable objects. These objects are generic function pointers. They include, not only function pointers and function references from C but also, lambdas, and functors.
Function Pointer
To start with, the following example illustrates the use of callback method using a simple function pointer. Here, “fp” is the type which denotes a function pointer. Such function must accept one integer argument and returns void type.
#include <iostream> //main header
using namespace std; //namespace
typedef void(*fp)(int); //fp is type pointer to function
void funda(int xvar)
{
cout << "Function called with :" << xvar << endl;
}
void testCallBack(fp ptr, int arg)
{
ptr(arg);
}
int main()
{
testCallBack(funda, 4);
return 0;
}
Output
Function called with :4
Problem with function pointers
Function pointers are very rigid and they require exact types of parameter and return value. These function pointer can neither accept a non-static class member nor a functor.
The following program uses same function pointer but this time, the program passes a functor. Clearly, the compiler shall generate an error in this case.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
typedef void(*fp)(int);
void funda(int xvar)
{
cout << "Function called with :" << xvar << endl;
}
class MainFunda
{
public:
void operator()(int xvar)
{
cout << "Functor called with :" << xvar << endl;
}
};
void testCallBack(fp ptr, int arg)
{
ptr(arg);
}
int main()
{
testCallBack(funda, 4); // funda is function pointer
MainFunda avar;
testCallBack(avar, 5); // avar is functor
return 0;
}
The error message is:
Callable Objects
These are like, generalized function pointers and they consist of anything which can be called like, a function. For example, lambda, functors, non-static member functions.
In C++11, a new template class std::function represents all such callable objects. This class is a type-safe wrapper for all the entities which we use just like function calls.
The following example, now defines “fp” as any callable object type which needs one integer parameter and returns void. We will see that this type now accept, not only function pointer but also lambda and functor.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
//typedef void(fp)(int);
using fp = std::function<void(int)>;
void funda(int xvar)
{
cout << "Function called with :" << xvar << endl;
}
class MainFunda
{
public:
void operator()(int xvar)
{
cout << "Functor called with :" << xvar << endl;
}
};
void testCallBack(fp ptr, int arg) //Accepts callable objects
{
ptr(arg);
}
int main()
{
testCallBack(funda, 4); //funda is Function pointer
MainFunda avar;
testCallBack(avar, 5); //avar is functor
auto lc = [](int xvar)
{
cout << "Lambda called with :"
<< xvar << endl;
};
testCallBack(lc, 6); //lc is lambda
return 0;
}
The output is:
Function called with :4
Functor called with :5
Lambda called with :6
Comparing callable objects with nullptr
The function callable objects support comparison with nullptr. This is because, if nothing has initialised a callable object, then it essentially become like a null object.
The following example, creates 2 function objects f1 & f2. And only one of them is initialized (with lambda). The other one will become equivalent to nullptr.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespace
//typedef void(fp)(int);
using fp = std::function<void(int)>;
void checkCallback(fp f)
{
if(f==nullptr)
cout << "No callback assigned" << endl;
else
cout << "callback assigned" << endl;
}
int main()
{
auto lc = [](int xvar)
{
cout << "Lambda called with :"
<< xvar << endl;
};
fp fvar1; //This callable object is empty
fp fvar2 = lc; //This callable object is not empty
checkCallback(fvar1);
checkCallback(fvar2);
return 0;
}
No callback assigned
callback assigned
Comparison of function callable objects not allowed
The std::function class do not allow comparison of two objects. Internally, the operator==( ) and operator!=( ) are deleted functions.
void compareCallbacks(fp fvar1, fp fvar2) //fp is callable
{
if(fvar1==fvar2) //error
{
}
if(fvar1!=fvar2) //error
{
}
}
Understanding the use of std::bind for callable objects
The function std::bind( ) also creates a callable object. Additionally, it maps the parameters of function objects with predefined values or with any of actual arguments passed in the call.
Bind the parameters with fixed values
The following function first creates a callable std::function object “fp” using a function pointer. And in second step, it uses bind to map parameter of this callable object with fixed values. Now, whenever, the callable object is called, it only uses mapped values.
#include <iostream> //main header
#include <functional> //for function ptr
using namespace std; //namespaces
using namespace std::placeholders;
void funda(int avar, int bvar, int cvar, int dvar)
{
cout << "\nfirst arg=" << avar << endl;
cout << "second arg=" << bvar << endl;
cout << "third arg=" << cvar << endl;
cout << "fourth arg=" << dvar << endl;
}
int main()
{
std::function<void(int, int, int, int)> fp = funda;
fp(1,2,3,4); //Before bind
fp = std::bind(fun, 10, 20, 30, 40);
fp(1,2,3,4); //After bind
return 0;
}
The output is:
first arg=1 => Before bind
second arg=2
third arg=3
fourth arg=4
first arg=10 => After bind
second arg=20
third arg=30
fourth arg=40
Using placeholders to map parameters with actual arguments
The nth argument passed to in actual call is denoted with “_n”. These actual arguments are denoted as placeholders. The placeholders can map any actual argument with any parameter of callable object.
For instance, the following example, there are 2 bind calls,
The previous call, maps second (_2) and fourth (_4) actual argument with corresponding parameters positions in the callable objects. And after that, the second bind function reverses that connection between second and forth mapping.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespaces
using namespace std::placeholders;
void funda(int avar, int bvar, int cvar, int dvar)
{
cout << "\nfirst arg=" << avar << endl;
cout << "second arg=" << bvar << endl;
cout << "third arg=" << cvar << endl;
cout << "fourth arg=" << dvar << endl;
}
int main()
{
std::function<void(int, int, int, int)> fp = funda;
fp(1,2,3,4);
fp = std::bind(funda, 10, _2, 30, _4);//second to second
//forth to forth
fp(1,2,3,4);
fp = std::bind(funda, 10, _4, 30, _2);//forth to second,
//second to forth
fp(1,2,3,4);
return 0;
}
The output is:
first arg=1
second arg=2
third arg=3
fourth arg=4
first arg=10
second arg=2
third arg=30
fourth arg=4
first arg=10
second arg=4
third arg=30
fourth arg=2
Application of std::bind( ) : Implementation of an Adaptor
The bind function can modify the mapping between actual arguments and the parameters of callable objects. Therefore, such feature helps in implementing an adaptor between 2 interfaces.
The following example, internally maps the inputs of a 2D object to corresponding inputs of a 3D object. Specifically, for completion, it maps the third parameter of 3D to a constant value. Finally, the 3D object’s draw becomes a callback for 2D object’s draw.
#include <iostream> //main header
#include <functional>//for function ptr
using namespace std; //namespaces
using namespace std::placeholders;
using cb2DType = std::function<void(int, int)>;
class mainfunda2D
{
public:
void setCallback(const cb2DType& cb)
{
drawCallback = cb;
}
void draw(int x, int y)
{
drawCallback(x,y);
}
private:
cb2DType drawCallback;
};
class mainfunda3D
{
public:
void draw (int x, int y, int z)
{
cout << "Draw on (" << x << ", " << y << ", " << z << ")" << endl;
}
};
int main()
{
mainfunda2D _2d;
mainfunda3D _3d;
_2d.setCallback(std::bind(&mainfunda3D::draw, _3d, _1, _2, 50) );
_2d.draw(4,5);
return 0;
}
The output is:
Draw on (4, 5, 50)