Unlike other languages such as Object Pascal, Ada, Java and C#, C++ does not offer
a native solution to the issue of passing a class's method as a callback function.
In the C language these are known as functors and exist very commonly in many
event driven applications. The main problem centers around the fact that multiple
instances of a particular class will result in different memory locations for
each instantiation. This leads to the need of having not only the method pointer
but also a pointer to the instance itself. The problem's definition brings about
an intuitive solution which falls within the realms of templates and compile time
instantiation and specialization.
Note 1: As of 2008 there are currently two proposed standard additions
that should rectify this issue and allow for C++ to natively
accommodate anonymous functions. These additions are lambdas and
std::function/std::bind.
The solution presented here is designed to take only one parameter.
However as more more compilers begin to correctly implement the future
C++ standard then variadic template arguments such as template lists
"..." (C++ Templates, The Complete Guide)
will become a reality allowing for frameworks that can easily cater
to arbitrary parameter sets. That said, once the single parameter
solution has been presented a crude workaround for multiple parameters
will also be discussed.
Usage of the template is very simple. The template itself can be instantiated as an object pointer
or just as a simple class. When being used as an object pointer, C++ has another painful limitation
and that is that the operator() can not be invoked without dereferencing the object pointer, a quick
and dirty solution was to place an execute() method within the template, which calls the operator()
from within the template itself. Other than that little problem, instantiating the SingularCallBack
as an object pointer will allow you to have a vector of callbacks, or any other kind of grouping which
is highly desirable in event driven programming.
Lets assume the following 2 classes exist, and that we want to have methodB() as our callback method.
From the code we can see that when methodB() is invoked with the paramter of class A, methodB() will
then invoke the method output() in class A. The proof that the callback really worked is if we see the
line "I am class A :D" in the stdout.
class A
{
public:
void output()
{
std::cout << "I am class A :D" << std::endl;
}
};
class B
{
public:
bool methodB(A a)
{
a.output();
return true;
}
};
There are two ways one can invoke the callback from an object pointer, the initial method is to derefrence
the object pointer and run the callback method (ie: () operator) the second option is to run the execute()
method.
A a;
B b;
SingularCallBack<B,bool,A>* cb;
cb = new SingularCallBack<B,bool,A>(&b,&B::methodB);
if((*cb)(a))
{
std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}
A a;
B b;
SingularCallBack<B,bool,A>* cb;
cb = new SingularCallBack<B,bool,A>(&b,&B::methodB);
if(cb->execute(a))
{
std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}
This is how you would instansiate and use the callback template as a normal object:
A more complicated example of how the callback template can be used is as follows:
class AClass
{
public:
AClass(unsigned int id)
: id_(id)
{}
bool AMethod(const std::string& str)
{
std::cout << "AClass[" << id_ << "]: " << str << std::endl;
return true;
}
private:
unsigned int id_;
};
typedef SingularCallBack <AClass, bool, const std::string&> ACallBack;
int main()
{
std::vector<ACallBack> callback_list;
AClass a1(1);
AClass a2(2);
AClass a3(3);
callback_list.push_back(ACallBack(&a1, &AClass::AMethod));
callback_list.push_back(ACallBack(&a2, &AClass::AMethod));
callback_list.push_back(ACallBack(&a3, &AClass::AMethod));
for (unsigned int i = 0; i < callback_list.size(); ++i)
{
callback_list[i]("abc");
}
for (unsigned int i = 0; i < callback_list.size(); ++i)
{
callback_list[i].execute("abc");
}
return 0;
}
A slightly more complicated example than the previous one shows how you can mix classes derived
from a common base class into a container and hence have the most derived method (originating from the base class)
invoked.
For the sake of simplicity and clarity, necessary code relating to instance validation has not been included,
in real-world implementations instances of classes should be passed around such frame-works using smart pointer
encapsulation classes. The STL provides two excellent choices the auto_ptr and its successor the shared_ptr. Also
in the book "Modern C++ Design" by Andrei Alexandrescu,
a policy design oriented smart-pointer class is made available. In all three cases each solution has its own
advantages and disadvantages so it is left up to the end user to decide which solution best meets their needs.
Below is a demonstration of a simple template pattern for overloading a typical CallBack class in order to present
multiple interfaces for callback methods of varying argument counts. For simplicity in the example below the CallBack class
can support methods with varying argument counts from 0 to 4, in theory more can be added.
Possible extensions to the SingularCallBack template would be to make the number of parameters for the
callback variable. At the moment the standard compilers such as GCC, Intel C++ compiler and Borland C++
Builder and BuilderX don't fully support templates as have been described in the proposed C++0x language
standard. Once the "..." syntax has been approved for variable template parameters and implemented in
the major compilers I will update this page for variable parameters. Until then the only way I can see
variable number of parameters being passed would be to overload template implementations of CallBack with
differing parameter counts, which in my opinion is a very inadequate solution.
If you found it difficult understanding the above mentioned syntax or think you might require a refresher
in method pointers and how they are used in the C++ language then the Simple C++ Callback
Examples might be just what you need to get started.
Templates and the whole meta-programming paradigm are truly powerful tools which
programmers of the C++ language and to a greater extent any other language which
supports the paradigm should and must take advantage of any chance they get. They
help solve complexities which would require the development of error prone and not
yet fully understood static software patterns. Templates encourage generic programming,
they motivate a programmer to think outside of their current view point and see future
uses of what they are building hence adding to the resuability of their code. However
not all problems are suited for template based solutions, and just like all things in
life too much of a good thing can make you sick. Being a good programmer not only
means knowing what solution will solve which problem, but also to knowing what solution
will cause more problems than the problem they are solving. In my opinion together with
portability the next most important factor which would make a programmer's code base
immortal would be the genericity of their code.
2006-07-21 Recently the TR1 proposal has begun to gain momentum, and is very likely to become
standardized within the next few years. As part of the TR1 proposal is the Bind mechanism/library found
in the BOOST C++ library. This form of Bind will definitely be the future in C++ with regards to callback
and delegate mechanisms. For production purposes it is advised that the Bind library be used.
Free use of the C++ Templated Callback Solution and the Simple C++ Callback Examples is permitted under the guidelines and in accordance with the most current
version of the "Common Public License."