On C + + callback function (callback) concise and practical

1 about callback functions

1.1 definitions

The definition of callback function can be very strict (complex) or simple (random). In fact, instead of studying the definition, it's better to discuss why callback function is needed and what callback function can do.
In my opinion, callback functions are not different from ordinary functions in the definition of functions, but in the way of calling, because in the final analysis, they are ordinary functions of all living beings in the code, that is, the focus of "callback function" is on the word "callback".
Take spending money as an example. Spending money on clothes is called consumption, and spending money on stocks is called investment. They all spend money, but in different ways, they have different meanings.

The following figure lists the differences between ordinary function execution and callback function call.

  • For ordinary functions, they are executed according to the logic and order set by the implementation.
  • For callback functions, it is assumed that Program A and program B are independently developed by two people respectively. The callback function Fun A2 is defined by Program A but called by program B. Program B is only responsible for calling Fun A2, but regardless of the specific function implementation of Fun A2 function.

1.2 why do I need a callback function

Because of this usage scenario, Fun A2 can only be executed when Fun B1 is called, which is a bit like the concept of interrupt function. Then someone may ask, can't you keep querying the status of Fun B1 in Program A and let Program A execute Fun A2 once it is executed? If you have such questions, you have already started.
The answer is "yes", but the implementation scheme is not good. Because Program A has been querying the status all the time in the whole process, which consumes a lot of resources. The query frequency is high, which costs CPU. The query frequency is low, and the real-time performance cannot be guaranteed. You only react after Fun B1 has been executed for a long time, and the execution of Fun A2 will be significantly later than Fun B1.
Because of this, the callback function is on the stage.

If you still know a little, you can see the answer of the Lord. What is a callback function?

2 how to implement function callback

The callback of the function is not complicated. Just tell Program B the address / pointer of the function of Fun A2.
In fact, what we want to discuss here is the types of callback functions commonly used in C + +.

Getting the address of the function is one of the key steps.
Ordinary functions and static member functions of classes are assigned definite function addresses, but ordinary functions of classes are shared by classes, and each instance of a class will not be assigned an independent member function, which is a waste of storage resources. Therefore, non static functions of classes are different as callback functions, which is also the focus of this article.

But we have to take it step by step, starting with a simple one.

2.1 ordinary function as callback function

#include <iostream>

void programA_FunA1() { printf("I'am ProgramA_FunA1 and be called..\n"); }

void programA_FunA2() { printf("I'am ProgramA_FunA2 and be called..\n"); }

void programB_FunB1(void (*callback)()) {
  printf("I'am programB_FunB1 and be called..\n");
  callback();
}

int main(int argc, char **argv) {
  programA_FunA1();

  programB_FunB1(programA_FunA2);
}

Execution results:

There's nothing to say. It's very simple.

2.2 static function of class as callback function

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  static void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
};

class ProgramB {
 public:
  void FunB1(void (*callback)()) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    callback();
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(ProgramA::FunA2);
}

results of enforcement

It can be seen that there is no essential difference between the above two methods.
However, this implementation has an obvious disadvantage: static functions cannot access non static member variables or functions, which will seriously limit the functions that callback functions can achieve.

2.3 non static function of class as callback function

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }
};

class ProgramB {
 public:
  void FunB1(void (ProgramA::*callback)(), void *context) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    ((ProgramA *)context->*callback)();
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(&ProgramA::FunA2, &PA);  // Add it here&
}

results of enforcement
![image.png](https://img-blog.csdnimg.cn/img_convert/87732e00b8f1fc0294f3468327763dfc.png
This method can get the expected results, which seems perfect, but it also has obvious shortcomings.
For example, in programB, FunB1 also uses the type of programA, that is, I also need to know the class definition of the callback function in advance. It is not easy to use when programB wants to encapsulate independently.

There is another way to avoid this problem. You can wrap a non static callback function into another static function. This method is also a widely used method.

#include <iostream>

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA2Wrapper(void *context) {
    printf("I'am ProgramA.FunA2Wrapper() and be called..\n");
    ((ProgramA *)context)->FunA2();  // FunA2() called here is a function of context, not this - > FunA2()
  }
};

class ProgramB {
 public:
  void FunB1(void (ProgramA::*callback)(), void *context) {
    printf("I'am ProgramB.FunB1() and be called..\n");
    ((ProgramA *)context->*callback)();
  }

  void FunB2(void (*callback)(void *), void *context) {
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback(context);
  }
};

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  ProgramB PB;
  PB.FunB1(&ProgramA::FunA2, &PA);  // Add it here&

  printf("\n");
  PB.FunB2(ProgramA::FunA2Wrapper, &PA);
}

Execution results:

Compared with the previous method, this method has no information about ProgramA in ProgramB, and is a more independent implementation.
FunB2() indirectly calls FunA2() by calling FunA2Wrapper(). Funa2 () can access and call any function and variable in the class. There is an additional wrapper function and some flexibility.

Although it is flexible to implement the callback with the wrapper function, it is not good enough, such as:
1) There is an additional wrapper function that is not very useful.
2) The incoming pointer is also cast in the wrapper.
3) When FunB2 is called, not only the address of wrapper function but also the address of PA should be specified.

Is there a more flexible and direct way? Yes, you can continue to look down.

3. Use of STD:: fusion and std::bind

STD:: fusion and std::bind are ready.
std::function is a general and polymorphic function encapsulation. The instance of std::function can store, copy, and call any callable target entity, including ordinary functions, Lambda expressions, function pointers, and other function objects [1].
The std::bind() function, like its function name, is used to bind some parameters of the function call [2].
You can use Baidu for their detailed usage. If necessary, you can write it separately in a later issue. Here you can go directly to the code to see how STD:: fusion and std::bind are used in callbacks.

#include <iostream>

#include <functional> // fucntion/bind

class ProgramA {
 public:
  void FunA1() { printf("I'am ProgramA.FunA1() and be called..\n"); }

  void FunA2() { printf("I'am ProgramA.FunA2() and be called..\n"); }

  static void FunA3() { printf("I'am ProgramA.FunA3() and be called..\n"); }
};

class ProgramB {
  typedef std::function<void ()> CallbackFun;
 public:
   void FunB1(CallbackFun callback) {
    printf("I'am ProgramB.FunB2() and be called..\n");
    callback();
  }
};

void normFun() { printf("I'am normFun() and be called..\n"); }

int main(int argc, char **argv) {
  ProgramA PA;
  PA.FunA1();

  printf("\n");
  ProgramB PB;
  PB.FunB1(normFun);
  printf("\n");
  PB.FunB1(ProgramA::FunA3);
  printf("\n");
  PB.FunB1(std::bind(&ProgramA::FunA2, &PA));
}

Execution output:

std::funtion supports the direct input of function address, or it can be specified through std::bind.
In short, STD:: Fun tion defines function types (input and output), and std::bind binds specific functions (specific functions to be called).

Compared with the wrapper method, this method is much more direct and concise.

Ref:

[1] https://blog.csdn.net/u013654125/article/details/100140328
[2] https://blog.csdn.net/u013654125/article/details/100140547

Keywords: C++ Programming

Added by matijarma on Thu, 30 Dec 2021 23:06:04 +0200