如何使用C++对象的成员函数作为 C 样式回调?

How to use a C++ object's member function as a C-style callback?

本文关键字:样式 回调 函数 C++ 何使用 对象 成员      更新时间:2023-10-16

我需要将对象实例函数用作没有参数的 C 样式回调,它不能是静态的,因为我需要访问实例变量并且我无法使用回调修改函数。

调用方函数的签名为:

void attachInterrupt(uint32_t pin, void (*callback)(void), uint32_t mode);

唉,我不能使用捕获列表,因为它是仅限 C 样式的回调,因此以下内容不起作用:

attachInterrupt(pin, [this]() { tracker(); }, mode);

我保留了一个指向对象实例的指针的静态数组,所以我尝试了这个:

static Actuator* actuator;
actuator = instance[index];
attachInterrupt(pin, [] { actuator->tracker(); }, mode);

问题是所有 lambda 函数都共享对存储在执行器中的指针的相同引用,但我需要每个 lambda 函数都使用它存储在 instance[index] 中的特定指针。

我让它正常工作的唯一方法是使用 switch 语句,但我想尽可能避免硬编码。

switch (index) {
case 0:
::attachInterrupt(pin, [] { instance[0]->tracker(); }, mode);
break;
case 1:
::attachInterrupt(pin, [] { instance[1]->tracker(); }, mode);
break;
...
}

必须有一种方法可以在不使用 switch 语句的情况下将数组特定静态元素的引用传递给 lambda 函数,我该如何实现?

如果你知道编译时要注册的回调数量,你可以将回调创建为模板函数:

template <size_t i>
static void callback()
{
instance[i]->tracker();
}

然后,您可以使用std::make_index_sequence和可变参数模板的解包语法一次性注册:

template <size_t... ids>
void register_all_callbacks_impl(std::index_sequence<ids...>)
{
// the (f(), 0) syntax calls a void function f but returns an int 0
// with this trick you can leverage the unpacking syntax
// to call void functions in an array initialization 
int dummy[] = { (attachInterrupt(pin, &callback<ids>, mode), 0)... };
(void)dummy; // omit "unused variable" warnings
}
constexpr size_t COUNT = ...;
void register_all_callbacks()
{
register_all_callbacks_impl(std::make_index_sequence<COUNT>());
}

您现在需要做的就是用大小COUNT初始化您的instance数组并调用register_all_callbacks()

注意:您需要 C++14 才能make_index_sequence

我可能会尝试类似的东西

template<uint32_t pin, uint32_t mode> class callback
{
public:
static void attach() { ::attachInterrupt(pin, &callback::tracker, mode); }
private:
// Make specialization for each combination
static void tracker();
}

然后要注册回调,您将执行以下操作:

callback<5, 3>::attach();
callback<7, 2>::attach();

如果模式是依赖于引脚号的常量,则应使用回调类的静态成员,然后为每个类类型初始化特定值。

const uint32_t callback<7>::mode_for_this_pin = 3; // maybe constexpr instead?

但由于这个问题没有包含太多细节,因此很难给出一个好的解决方案。

我们假设在调用回调时不可能知道引脚,因此由客户端将特定函数关联到每个引脚。

或者,您也可以向类中添加一个static std::function<void()> mfn成员,然后像这样初始化它:

callback<7, 3>::mfn = [] { your code here };

如果要执行的回调取决于某些条件,这可能很有用。

您的问题没有通用的答案,因为语言不直接支持这一点。最佳解决方案取决于您的问题中未记录的多个因素。

显然,如果特定引脚的功能始终相同,则可以轻松地pin3_callbackpin3_mode2_callback功能,并在实现中执行任何适当的操作。

问题中未指定的其他要点何时应进行初始化以及是否需要进行一些清理。

我知道在 Windows 下,过去使用一些汇编代码来生成独特的函数,以应对无法向回调提供用户数据的情况。