C++ Callback Solution

The Problem

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.

The Solution

The solution is designed to take in only 1 parameter, but hopefully as more compilers begin to fully implement the newer C++ standard, variadic template arguments such as template lists "..." (C++ Templates, The Complete Guide), there will then be the ability to cater for variable length argument set.


template < class Class, typename ReturnType, typename Parameter >
class SingularCallBack
{
public:

 typedef ReturnType (Class::*Method)(Parameter);

 SingularCallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method        = _method;
 };

 ReturnType operator()(Parameter parameter)
 {
    return (class_instance->*method)(parameter);
 };

 ReturnType execute(Parameter parameter)
 {
    return operator()(parameter);
 };

private:

 Class*  class_instance;
 Method  method;
};

Usage Of The Template

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 a;
B b;
SingularCallBack< B,bool,A >cb(&b,&B::methodB);

if(cb(a))
{
   std::cout << "CallBack Fired Successfully!" << std::endl;
}
else
{
   std::cout << "CallBack Fired Unsuccessfully!" << std::endl;
}

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 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.


class BaseClass
{
   public:

     virtual ~BaseClass(){};
     virtual bool DerivedMethod(const std::string& str){ return true; };
};

class AClass : public BaseClass
{
public:

  AClass(unsigned int _id): id(_id){};
 ~AClass(){};

  bool AMethod(const std::string& str)
  {
     std::cout << "AClass[" << id << "]: " << str << std::endl;
     return true;
  };

  bool DerivedMethod(const 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(const std::string& str)
  {
     std::cout << "BClass[" << id << "]: " << str << std::endl;
     return true;
  };

  bool DerivedMethod(const 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 0;
}


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.


Variable Argument CallBack Template

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.


template < class Class, typename ReturnType, typename Parameter1 = void,
                                             typename Parameter2 = void,
                                             typename Parameter3 = void,
                                             typename Parameter4 = void >
class CallBack
{
public:

 typedef ReturnType (Class::*Method)(Parameter1, Parameter2, Parameter3, Parameter4);

 CallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method         = _method;
 };

 ReturnType operator()(Parameter1 p1, Parameter2 p2, Parameter3 p3, Parameter4 p4)
 {
    return (class_instance->*method)(p1, p2, p3, p4);
 };

 ReturnType execute(Parameter1 p1, Parameter2 p2, Parameter3 p3, Parameter4 p4)
 {
    return operator()(p1, p2, p3, p4);
 };

 private:

   Class*  class_instance;
   Method  method;
};



template < class Class, typename ReturnType, typename Parameter1,
                                             typename Parameter2,
                                             typename Parameter3>
class CallBack  < Class, ReturnType, Parameter1, Parameter2, Parameter3 >
{
public:

 typedef ReturnType (Class::*Method)(Parameter1, Parameter2, Parameter3);

 CallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method         = _method;
 };

 ReturnType operator()(Parameter1 p1, Parameter2 p2, Parameter3 p3)
 {
    return (class_instance->*method)(p1, p2, p3);
 };

 ReturnType execute(Parameter1 p1, Parameter2 p2, Parameter3 p3)
 {
    return operator()(p1, p2, p3);
 };

 private:

   Class*  class_instance;
   Method  method;
};


template < class Class, typename ReturnType, typename Parameter1, typename Parameter2 >
class CallBack < Class, ReturnType, Parameter1, Parameter2 >
{
public:

 typedef ReturnType (Class::*Method)(Parameter1,Parameter2);

 CallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method         = _method;
 };

 ReturnType operator()(Parameter1 p1, Parameter2 p2)
 {
    return (class_instance->*method)(p1, p2);
 };

 ReturnType execute(Parameter1 p1, Parameter2 p2)
 {
    return operator()(p1, p2);
 };

 private:

   Class*  class_instance;
   Method  method;
};


template < class Class, typename ReturnType, typename Parameter>
class CallBack < Class, ReturnType, Parameter >
{
public:

 typedef ReturnType (Class::*Method)(Parameter);

 CallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method         = _method;
 };

 ReturnType operator()(Parameter p1)
 {
    return (class_instance->*method)(p1);
 };

 ReturnType execute(Parameter p1)
 {
    return operator()(p1);
 };

 private:

   Class*  class_instance;
   Method  method;
};


template < class Class, typename ReturnType>
class CallBack < Class, ReturnType >
{
public:

 typedef ReturnType (Class::*Method)(void);

 CallBack(Class* _class_instance, Method _method)
 {
    class_instance = _class_instance;
    method         = _method;
 };

 ReturnType operator()()
 {
    return (class_instance->*method)();
 };

 ReturnType execute()
 {
    return operator()();
 };

 private:

   Class*  class_instance;
   Method  method;
};


The above template patterns can be invoked as follows:


class AClass
{
public:

  AClass(unsigned int _id): id(_id){};
 ~AClass(){};

  bool AMethod(const std::string& str)
  {
     std::cout << "AClass[" << id << "]: " << str << std::endl;
     std::cout.flush();
     return true;
  };

  bool method4(int a, int b, int c, int d)
  {
     std::cout << "Method - 4" << std::endl;
     return true;
  };

  bool method3(int a, int b, int c)
  {
     std::cout << "Method - 3" << std::endl;
     return true;
  };

  bool method2(int a, int b)
  {
     std::cout << "Method - 2" << std::endl;
     return true;
  };

  bool method1(int a)
  {
     std::cout << "Method - 1" << std::endl;
     return true;
  };

  bool method0()
  {
     std::cout << "Method - 0" << std::endl;
     return true;
  };

  void method()
  {
     std::cout << "Method - v" << std::endl;
  };

private:

 unsigned int id;
};

typedef CallBack< AClass, bool, int, int ,int, int > callback_type4;
typedef CallBack< AClass, bool, int, int ,int > callback_type3;
typedef CallBack< AClass, bool, int, int > callback_type2;
typedef CallBack< AClass, bool, int > callback_type1;
typedef CallBack< AClass, bool > callback_type0;
typedef CallBack< AClass, void > callback_typev;

int main()
{

   AClass aclass(1);

   callback_type4 cb4(&aclass, &AClass::method4);
   callback_type3 cb3(&aclass, &AClass::method3);
   callback_type2 cb2(&aclass, &AClass::method2);
   callback_type1 cb1(&aclass, &AClass::method1);
   callback_type0 cb0(&aclass, &AClass::method0);
   callback_typev cbv(&aclass, &AClass::methodv);

   std::vector< std::pair< int ,void* > > callback_list;

   callback_list.push_back(std::pair< int ,void* >( 4, static_cast(&cb4)));
   callback_list.push_back(std::pair< int ,void* >( 3, static_cast(&cb3)));
   callback_list.push_back(std::pair< int ,void* >( 2, static_cast(&cb2)));
   callback_list.push_back(std::pair< int ,void* >( 1, static_cast(&cb1)));
   callback_list.push_back(std::pair< int ,void* >( 0, static_cast(&cb0)));
   callback_list.push_back(std::pair< int ,void* >(-1, static_cast(&cbv)));


   for (unsigned int i = 0; i < callback_list.size(); ++i)
   {
      switch (callback_list[i].first)
      {
         case  4: (*static_cast< callback_type4* >(callback_list[i].second))(1,2,3,4);
                  break;

         case  3: (*static_cast< callback_type3* >(callback_list[i].second))(1,2,3);
                  break;

         case  2: (*static_cast< callback_type2* >(callback_list[i].second))(1,2);
                  break;

         case  1: (*static_cast< callback_type1* >(callback_list[i].second))(1);
                  break;

         case  0: (*static_cast< callback_type0* >(callback_list[i].second))();
                  break;

         case -1: (*static_cast< callback_typev* >(callback_list[i].second))();
                  break;
      }
   }


   for (unsigned int i = 0; i < callback_list.size(); ++i)
   {
      switch (callback_list[i].first)
      {
         case  4: static_cast< callback_type4* >(callback_list[i].second)->execute(1,2,3,4);
                  break;

         case  3: static_cast< callback_type3* >(callback_list[i].second)->execute(1,2,3);
                  break;

         case  2: static_cast< callback_type2* >(callback_list[i].second)->execute(1,2);
                  break;

         case  1: static_cast< callback_type1* >(callback_list[i].second)->execute(1);
                  break;

         case  0: static_cast< callback_type0* >(callback_list[i].second)->execute();
                  break;

         case -1: static_cast< callback_typev* >(callback_list[i].second)->execute();
                  break;
      }
   }

   return 0;
}


Future Directions

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.

A Quick Note

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.

A Final Thought

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.

An Update

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.

C++ Templated Callback Solution License

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."


Compatability

The C++ Templated Callback Solution and the Simple C++ Callback Examples are both known to be compatible with the following C++ compilers:

  • GNU Compiler Collection (3.3.1-x+)
  • Intel® C++ Compiler (8.x+)
  • Microsoft Visual Studio C++ Compiler (7.x+)

Download

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Callback(回调函数)是一种常见的编程技术,它允许一个函数作为参数传递给另一个函数,以便在某个事件发生时被调用。在 C++ 中,回调函数可以使用函数指针或函数对象实现。 使用函数指针实现回调函数的示例代码如下: ```cpp #include <iostream> using namespace std; void callback(int val) { cout << "Callback function called with value: " << val << endl; } void doSomething(int val, void (*func)(int)) { cout << "Doing something with value: " << val << endl; func(val); } int main() { doSomething(42, callback); return 0; } ``` 上述代码中,`doSomething` 函数接收一个整数和一个指向回调函数的函数指针。在函数内部,该整数被处理并传递给回调函数。在主函数中,我们调用 `doSomething` 函数并传递一个回调函数 `callback`。 另一种实现回调函数的方法是使用函数对象。这种方法比使用函数指针更加灵活,因为函数对象可以存储状态并支持函数重载。下面是使用函数对象实现回调函数的示例代码: ```cpp #include <iostream> using namespace std; class Callback { public: void operator()(int val) const { cout << "Callback functor called with value: " << val << endl; } }; void doSomething(int val, const Callback& func) { cout << "Doing something with value: " << val << endl; func(val); } int main() { Callback callback; doSomething(42, callback); return 0; } ``` 在上述代码中,我们定义了一个 `Callback` 类,它重载了函数调用运算符。`doSomething` 函数接收一个整数和一个 `Callback` 对象作为参数。在函数内部,整数被处理并传递给回调函数对象。在主函数中,我们创建一个 `Callback` 对象并将其传递给 `doSomething` 函数。 无论是使用函数指针还是函数对象,回调函数都是一种非常有用的技术,可以让程序在特定事件发生时执行特定的操作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值