使用带有闭包的 lambda 的回调



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

我遇到的问题是如何将">函数指针"存储到">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 函数(捕获实例(存储到类型化函数指针。如果 lambda 是一个无捕获的 lambda,你可以将它转换为函数指针(很容易(。

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

非泛型 lambda 表达式的闭包类型,没有满足约束(如果有(的 lambda 捕获具有 将函数转换为具有C++语言链接的指针到函数 具有与闭包类型相同的参数和返回类型 函数调用运算符。

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


解决方案 - 1

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

typedef void(*ExtiHandler)();

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

解决方案 - 2

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

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

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

用法:见现场示例

  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. 无法将参数包转发到特征。 即, 无法执行以下操作:

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

仅当 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;
}
};

如果您没有 std 库,那么您可以自己实现类型擦除。

像这样的东西...

#include <iostream>
#include <memory>
struct function
{
struct base
{
virtual void call() = 0;
virtual base* clone() = 0;
};
template <typename Fn>  
struct impl : base
{
Fn fn_;
impl(Fn&& fn) : fn_(std::forward<Fn>(fn)){}
impl(Fn& fn) : fn_(fn){}
virtual void call()
{
fn_();
}
virtual base* clone() { return new impl<Fn>(fn_); }
};
base* holder_;
function() : holder_(nullptr)
{};
template <typename Fn>
function(Fn&& fn) : holder_(nullptr)
{
holder_ = new impl<Fn>(std::forward<Fn>(fn));
}
function( function&& other)
{
holder_ = other.holder_;
other.holder_ = nullptr;
}
function(const function& other)
{
holder_ = other.holder_->clone();
}
~function()
{
if (holder_) delete holder_;
}

function& operator=(function&& other)
{
if (holder_) delete holder_;
holder_ = other.holder_;
other.holder_ = nullptr;
return *this;
}
function& operator=(const function& other)
{
if (holder_) delete holder_;
holder_ = other.holder_->clone();
return *this;
}
void operator()()
{
holder_->call();
}
};

class Gpio
{
public:
using ExtiHandler = function;   
//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()
{
std::cout << "Button pressed" << std::endl;
}
void init()
{
pin.enable_irq([this]() { this->button_pressed(); });
}
};
int main() {
Gpio some_pin;
Button b(some_pin);
b.init();
some_pin.handler();
return 0;
}

演示

最新更新