使用带闭包的 lambda 回调

kni*_*ick 5 c++ lambda closures function-pointers c++11

我正在尝试实现一个回调,该回调将控制从中断服务例程传递到 c++ 类上的成员函数。我认为lambdas和闭包将是一种方便的方法,但我在实现它时遇到了麻烦。下面是我的代码的简化版本。

我遇到的问题是如何将“函数指针”存储到“ lambda ”。

class Gpio
{
  public:
    typedef void (*ExtiHandler)();    
  private:
    ExtiHandler handler;    
  public:
    void enable_irq(ExtiHandler handler_in) 
    { 
      // enable interrupt
      // ...
      // save handler so callback can be issued later
      handler = handler_in;
    }
};

class Button
{
  private:
    Gpio& pin;    
  public:
    Button(Gpio& pin_in) : pin(pin_in) 
    {
    };

    void button_pressed()
    {
      // do something
    }

    void init()
    {
      pin.enable_irq([this]() { this->button_pressed(); });
    }
};
Run Code Online (Sandbox Code Playgroud)

编译失败并显示以下错误消息;

 no matching function for call to 'Gpio::enable_irq(Button::init()::<lambda()>)'candidate: void Gpio::enable_irq(Gpio::ExtiHandler) no known conversion for argument 1 from 'Button::init()::<lambda()>' to 'Gpio::ExtiHandler {aka void (*)()}' Build failed
Run Code Online (Sandbox Code Playgroud)

如何修改此代码以解决编译错误?

JeJ*_*eJo 5

问题是,该enable_irq函数需要一个类型化的函数指针void (*ExtiHandler)() 而不是 lambda函数。

也就是说,这里

pin.enable_irq([this]() { this->button_pressed(); });
Run Code Online (Sandbox Code Playgroud)

您正在尝试将 lambda 函数(捕获实例)存储到类型化函数指针。如果它是一个无捕获的 lambda,您可以将 lambda 转换为函数指针(很容易)。

参见[expr.prim.lambda.closure] (第 7 节)

没有满足约束(如果有)的非泛型 lambda 表达式的闭包类型具有一个转换函数,指向具有 C++ 语言链接的函数的指针,该函数具有与闭包类型的函数调用运算符相同的参数和返回类型.

由于lambda 不只是普通函数并且捕获它需要保留状态,因此您找不到任何简单或常规的解决方案来使它们分配给函数指针。


解决方案 - 1

最简单的解决方案是std::function通过支付某种类型擦除开销来代替使用。这意味着,在您的代码中,只需要更改

 typedef void(*ExtiHandler)();
Run Code Online (Sandbox Code Playgroud)

typedef std::function<void()> ExtiHandler;
// or
// using ExtiHandler = std::function<void()>;
Run Code Online (Sandbox Code Playgroud)

解决方案 - 2

这可以在不使用 STL 的情况下完成吗?

是的。在对这个主题进行了一些小的研究之后,我想出了一个类型特征解决方案来存储带有闭包lambdas到等效的类型化函数指针。

#include <iostream>

template<typename Lambda> struct convert_lambda : convert_lambda<decltype(&Lambda::operator())> {};    
template<typename Lambda, typename ReType, typename... Args>
struct convert_lambda<ReType(Lambda::*)(Args...) const>
{
    using  funPtr = ReType(*)(Args...);
    static funPtr make_function_ptr(const Lambda& t)
    {
        static const Lambda& lmda = t;
        return [](Args... args) {   return lmda(args...);   };
    }
};    
template<typename Lambda> using convert_lambda_t = typename convert_lambda<Lambda>::funPtr;    
template<typename Lambda> constexpr convert_lambda_t<Lambda> make_function_ptr(const Lambda& t)
{
    return convert_lambda<Lambda>::make_function_ptr(t);
}
Run Code Online (Sandbox Code Playgroud)

用法: SEE LIVE EXAMPLE

  1. 您现在可以简单地继续您的GpioButton课程,而无需更改任何内容。:

    pin.enable_irq(make_function_ptr([this]() { this->button_pressed(); })); 
    // or 
    // pin.enable_irq(make_function_ptr([&]() { this->button_pressed();})); 
    
    Run Code Online (Sandbox Code Playgroud)
  2. 或有论据。例如

    int aa = 4;
    auto lmda = [&aa](const int a, const float f) { std::cout << a * aa * f << std::endl; };
    void(*fPtrTest)(const int, const float) = make_function_ptr(lmda);
    fPtrTest(1, 2.0f);
    
    Run Code Online (Sandbox Code Playgroud)

缺点:解决方案 - 2:

  1. 能够识别的说明符的可选序列(即mutableconstexpr

  2. 不是能够转发参数包的性状。即,以下情况是不可能的:

    return [](Args&&... args) { return lmda(std::forward<Args>(args)...); };
    
    Run Code Online (Sandbox Code Playgroud)


raf*_*x07 3

仅当 lambda 捕获列表为空时,才可以将闭包对象分配给函数指针,在您的情况下,不满足此条件 - [this]

您可以用作std::function包装器来存储您的闭包:

#include <functional>

class Gpio
{
  public:
        using ExtiHandler = std::function<void()>;
  private:
        std::function<void()> handler;
  public:
    void enable_irq(const ExtiHandler& handler_in) 
    {
      handler = handler_in;
    }
};
Run Code Online (Sandbox Code Playgroud)