C++ binds Lambda expressions to callback functions

Lambda expression and callback function

Lambda expression (Lambda) is a new feature of C++11 standard. Lambda is a simple way to define anonymous function objects. Lambda is usually passed to algorithms or asynchronous methods as parameters.
For Lambda Expression, please refer to Lambda Expressions in C++
For the content of function object, please refer to: Function Objects in the C++ Standard Library

Consider the function for creating threads in Unix systems:

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)

The type of parameter start_routine is a callback function pointer to a function executed by a thread. The parameter arg is the parameter passed to the callback function. If callback functions require multiple parameters, they can be stored in structured bodies. The following example code demonstrates that creating a new thread calculates the sum of all elements of an array:

#include <pthread.h>
#include <iostream>
using namespace std;

struct sum_args
{
   const int* arr;
   const size_t size;
   int* sum;
};

void* sum_routine(void* arg)
{
   sum_args* args = (sum_args*)arg;
   *(args->sum) = 0;
   for(size_t i = 0; i < args->size; ++i)
       *(args->sum) += args->arr[i];
   return nullptr;
}

int main()
{
   const size_t N = 3;
   const int arr[N] = {10, 20, 30};
   int sum;
   sum_args args = {arr, N, &sum};
   pthread_t tid;
   int err = pthread_create(&tid, nullptr, sum_routine, &args);
   pthread_join(tid, NULL);
   cout << "sum : " << sum << endl;
   return 0;
}

If you can pass the Lambda expression to pthread_create, the code becomes concise and easy to read, as follows:

int err = pthread_create(nullptr, nullptr, [=, &arr, &sum]() {
   sum = 0;
   for(size_t i = 0; i < N; ++i)
   	sum += arr[i];
}, ...);

However, the parameter start_routine of pthread_create is a function pointer, while Lambda is an object, and there is no conversion between object (object pointer) and function pointer. Notice that the parameter arg of pthread_create is void*, and Lambda is an object, so an address runner can be used for a Lambda to pass the address of Lambda to arg as a parameter. Refer to the following sample code:

  auto add = [](int x, int y) {return x + y; };
  void* pv = &add;     			// Lambda address conversion to void*
  auto addptr = (decltype(&add))pv; 	// Converting void* to Lambda pointer
  int z = (*addptr)(2, 3);   		// Call Lambda

Now rewrite the callback function to give it the ability to recognize and invoke Lambda. Refer to the following sample code:

template <typename Fx> void *start_routine_t(void* arg)
{
    Fx* f = (Fx*)arg;
    return (*f)();
}

int main()
{
    int x = 2, y = 3, z1, z2;
    auto add = [=, &z1]()
    {
        z1 = x + y;
        return nullptr;
    };
    auto sub = [=, &z2]()
    {
        z2 = x - y;
        return nullptr;
    };
    pthread_t tid1, tid2;
    int err = pthread_create(&tid1, nullptr, start_routine_t<decltype(add)>, &add);
    pthread_join(tid1, nullptr);
    err = pthread_create(&tid2, nullptr, start_routine_t<decltype(sub)>, &sub);
    cout << "z1 : " << z1 << ", z2 : " << z2 << endl;
    return 0;
}

Tags: Lambda Unix

Posted on Mon, 07 Oct 2019 13:08:44 -0400 by samirk