Unlike other languages such as Object Pascal and Ada, 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.
The solution is designed to take in only 1 parameter, but hopefully as more compilers
begin to fully implement the C++ standard, dynamic template arguments such as template
lists "..." (C++ Templates, The Complete Guide), then there will be room for an undefined
argument set.
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 more complicated example of how the callback template can be used is as follows:
class AClass
{
public:
AClass(unsigned int _id): id(_id){};
~AClass(){};
bool AMethod(std::string str)
{
std::cout << "AClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
typedef SingularCallBack < AClass, bool, 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 true;
}
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.
class BaseClass
{
public:
virtual ~BaseClass(){};
virtual bool DerivedMethod(std::string str){ return true; };
};
class AClass : public BaseClass
{
public:
AClass(unsigned int _id): id(_id){};
~AClass(){};
bool AMethod(std::string str)
{
std::cout << "AClass[" << id << "]: " << str << std::endl;
return true;
};
bool DerivedMethod(std::string str)
{
std::cout << "Derived Method AClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
class BClass : public BaseClass
{
public:
BClass(unsigned int _id): id(_id){};
~BClass(){};
bool BMethod(std::string str)
{
std::cout << "BClass[" << id << "]: " << str << std::endl;
return true;
};
bool DerivedMethod(std::string str)
{
std::cout << "Derived Method BClass[" << id << "]: " << str << std::endl;
return true;
};
private:
unsigned int id;
};
typedef SingularCallBack < BaseClass, bool, std::string > BaseCallBack;
int main()
{
std::vector < BaseCallBack > callback_list;
AClass a(1);
BClass b(2);
callback_list.push_back(BaseCallBack(&a, &BaseClass::DerivedMethod));
callback_list.push_back(BaseCallBack(&b, &BaseClass::DerivedMethod));
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 true;
}
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.
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."