c++ - 使用带闭包的 lambda 回调

标签 c++ c++11 lambda closures function-pointers

我正在尝试实现一个回调,它将控制权从中断服务例程传递到 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(); });
    }
};

编译失败,出现以下错误信息;

 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

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

最佳答案

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

也就是说,这里

pin.enable_irq([this]() { this->button_pressed(); });

您正在尝试将 lambda 函数(通过捕获实例)存储到 typed function pointer .如果 lambda 是无捕获的 lambda,您可以(轻松地)将 lambda 转换为函数指针。

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

The closure type for a non-generic lambda-expression with no lambda-capture whose constraints (if any) are satisfied has a conversion function to pointer to function with C++ language linkage having the same parameter and return types as the closure type's function call operator.

lambdas are not just ordinary functions捕获它需要保持状态, 您找不到任何简单或常规的解决方案来使它们分配给函数指针。


解决方案 - 1

最简单的解决方案是使用 std::function 相反,通过支付一些 type erasure overhead .这意味着,在您的代码中,只需更改

 typedef void(*ExtiHandler)();

typedef std::function<void()> ExtiHandler;
// or
// using ExtiHandler = std::function<void()>;

解决方案 - 2

Can this be accomplished without using the STL?

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

#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);
}

用法: 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();})); 
    
  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);
    

缺点:解决方案 - 2:

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

  2. 能够将参数包转发给特征。 IE, 以下是不可能的:

    return [](Args&&... args) { return lmda(std::forward<Args>(args)...); };
    

关于c++ - 使用带闭包的 lambda 回调,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52462864/

相关文章:

c++ - 在 C++ 中定义虚继承的两种不同方式

c++ - Unicode 与 MSVC++ 2012

c++ - 高斯模糊 C++(无法显示完整图像)

C++:使用 auto 还是 typedef?

java - 如何在 Java 中迭代 lambda 函数

C++ 加密解密函数的 Java 实现(Cryptix 工具箱)

c++ - 我如何用一个值填充 C++ 中的矩阵?

c++ - 将 nullptr 分配给函数指针变量是否安全?

c++ - 为什么在某些 STL 容器中我们不能直接使用 lambda 而不提及它的类型?

c++ - 为什么将 lambda 用于非类型模板参数时 gcc 会失败?