C++如何在C-style注册回调函数参数中使用lambda表达式,C风格的回调函数传递lambda表达式 作为参数

假设有一个已经开发的程序,它有一个需要回调函数参数,类型定义如下:

void set_onConnect(void(*cb)(void *), void * userdata);

    希望可以注册C++11的lambda表达式,而且是带捕获变量的lambda表达式,因为要用捕获变量来维持状态。

首先这个注册函数代码如下:

void set_onConnect(void(*cb)(void *), void * userdata) {
    //这里是一个简单的模拟。实际中可能会多次调用callback函数。
    cb(userdata); //  测试
}

这个注册回调函数第一个参数是C-style函数指针,不带状态。第二个参数void *userdata,携带函数执行的用户特别数据。

这样每次函数执行的时候,会将userdata传递进来,做到了持续保持对用户特别数据的访问和修改。

对于将lambda表达式与函数指针之间的转换,如果没有捕获变量可以直接转换。

void raw_pointer_test() {
  int x = 0;
  auto f = [](void* userdata)->void {
      int *x = static_cast<int*>(userdata);
      ++(*x); 
  };
  set_onConnect(f, &x);
  std::cout << x << "\n";
}

// 如下调用,没有问题
raw_pointer_test();

这种转换方式,完全属于C风格,存在类型不安全。同时,如果想要使用lambda表达式来直接捕获变量x,则不行。

下面这个代码无法通过编译。

void raw_function_pointer_capture_test() {
  int x = 0;
  auto f = [x](void* userdata) mutable ->void {
      ++x; 
  };
  set_onConnect(f, nullptr);
  std::cout << x << "\n";
}

 

error C2664: “void set_onConnect(void (__cdecl *)(void *))”: 无法将参数 1 从“raw_function_pointer_capture_test::<lambda_c8e93fc7ce8a8ac1a784556cc3260ec1>”转换为“void (__cdecl *)(void *)”

 那有什么方法能够将捕获变量的lambda表达式转换成普通函数指针,同时能够保留状态呢?

方法一: 声明一个全局的invoke_function函数,将lambda表达式转为为void*,即将lambda表达式作为状态传递。
extern "C" void invoke_function(void* ptr) {
    (*static_cast<std::function<void()>*>(ptr))();
}

void lambda_to_function(){
    int x = 0;
    auto lambda_f = [&]()->void { ++x; };
    std::function cpp_function(std::move(lambda_f));
    register_callback(&invoke_function, &cpp_function);
    std::cout << x << "\n";
}

//调用代码
lambda_to_function();

std::function cpp_function用于接管lambda表达式的所有权,状态都存在这里。此处使用的是栈变量,可以根据实际的需要变成堆变量,防止cpp_function析构后再使用,成为undefined behavior。

方法二:使用模板,将状态存在一个结构体里面。
#include <iostream>
#include <tuple>
#include <memory>

template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};
template<typename... Args, typename Lambda>
callback<Args...> voidify( Lambda&& l ) {
  using Func = typename std::decay<Lambda>::type;
  std::unique_ptr<void, void(*)(void*)> data(
    new Func(std::forward<Lambda>(l)),
    +[](void* ptr){ delete (Func*)ptr; }
  );
  return {
    +[](void* v, Args... args)->void {
      Func* f = static_cast< Func* >(v);
      (*f)(std::forward<Args>(args)...);
    },
    std::move(data)
  };
}


void lambda_capture_template_test() {
  int x = 0;
  auto closure = [&]()->void { ++x; };
  auto voidified = voidify(closure);
  register_callback( voidified.function, voidified.state.get() );
//   register_callback( voidified.function, voidified.state.get() );
  std::cout << x << "\n";
}

//调用代码
lambda_capture_template_test();

template<class...Args>
struct callback {
  void(*function)(void*, Args...)=nullptr;
  std::unique_ptr<void, void(*)(void*)> state;
};

这个模板类callback,第一个成员就是普通函数指针,用于注册回调函数时使用。第二个成员是自定义deleter的unique_ptr,智能指针管理的是一个匿名类(即lambda表达式所属的类)。 

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值