让一个对象合并两个函数并将它们作为回调传递? (C++)

Having an object merge two functions and passing them as a callback? (C++)

我想让我的 class 接受一个函数“回调存根”,添加一些在存根之前和之后处理的东西,然后将整个东西作为回调传递给中断。不同的存根可以附加到 class.

的不同实例

我收到如下评论中所示的错误。它采用函数指针,将其存储为属性,然后将其传递给 attachInterrupt 函数。不过加个小前奏也没用

setInterrupt() 中,我得到 指向绑定函数的指针只能用于调用 functionC/C++(300)

altSetInterrupt() 但是编译正常。

问题:是否有 * & 或 -> 的组合可以使这个工作?

已编辑:消除 Jerry Jeremiah 指出的愚蠢错误

#include <Arduino.h>

#define MYPIN 1

// Declarations
class myClass {
    public:
    myClass();
    void setInterrupt();
    void altSetInterrupt();
    void setCallBackStub( void (*cb)());

    private:
    void (*myCallBackStub)();
    void myCallBack();
};

void mySillyCB();

// Definitions
myClass::myClass():
    myCallBackStub(nullptr)
    {}

void myClass::setCallBackStub( void (*cb)()) {
    myCallBackStub = cb;
}

void myClass::myCallBack() {
    Serial.println("Wohoo");
    myCallBackStub(); 
}


void myClass::setInterrupt() {
    attachInterrupt(MYPIN, this->myCallBack, HIGH); // a pointer to a bound function may only be used to call the functionC/C++(300)
}

void myClass::altSetInterrupt() {
    attachInterrupt(MYPIN, this->myCallBackStub, HIGH); // no compiler error here
}


void mySillyCB() {
    Serial.println("Called Back");
}

// Code
myClass theThing;

void setup() {
    theThing.setCallBackStub( mySillyCB);
    theThing.setInterrupt();
}

void loop() {}

In myCallBack(), I get identifier "myCallBackStub" is undefinedC/C++(20)

您已将此定义为一个自由函数,而不是实现一个方法。你有:

void myCallBack() {

但你应该:

void myClass::myCallBack() {

因此,范围内没有这样的名称 myCallBackStub

In setInterrupt(), I get a pointer to a bound function may only be used to call the functionC/C++(300)

this->myCallBack 不是 C++ 中的 first-class 值;它是绑定到特定实例 (*this) 的成员函数。你唯一能做的就是调用它(比如 this->myCallBack())。

值得注意的是,您不能将其转换为函数指针。这是因为 non-static 成员函数需要一个实例来调用函数,而自由函数指针(attachInterrupt 接受的)没有额外的机制来传达这种状态。即使您对 pointer-to-member-function (&myClass::myCallBack) 使用了正确的语法,这仍然无法正常工作,因为您无法在 pointer-to-function 和 pointer-to-member-function.

之间进行转换

可以传递this->myCallBackStub因为这实际上是一个函数指针值。


因为 attachInterrupt() 状态无论如何都是全局的,你不会通过 而不是 使你的机制全局化来获得很多。您 运行 遇到的主要挑战是回调函数不接受任何参数,因此您甚至无法判断使用了哪个 pin/mode。

为了解决这个问题,每个 pin/mode 组合需要一个不同的 non-member 回调函数,并且必须在编译时 提前 知道这些,因为您(大概)不想在运行时陷入发射蹦床的复杂性。

这可以在编译时使用宏来完成。宏的每次调用都应该做几件事:

  • 声明一个全局回调函数。
  • 声明将由全局回调函数调用的回调函数的全局向量objects。
  • 可能会调用 attachInterrupt(),具体取决于是否可以在静态初始化期间完成。

这是此概念的示例实现,可能需要进行调整才能在生产中发挥作用。您可以使用以下内容创建 header,例如 interrupts.hpp

#include <functional>
#include <vector>

// These types may need adjusting to match Arduino's types.
using Pin_t = int;
using Mode_t = int;

using InterruptCallback_t = std::function<void()>;
using InterruptCallbackList_t = std::vector<InterruptCallback_t>;

using RawInterruptCallback_t = void (*)();

#define DECLARE_INTERRUPT(pin, mode) DECLARE_INTERRUPT_(pin, mode)
#define DECLARE_INTERRUPT_(pin, mode) \
    void add_interrupt_callback_##pin##_##mode (InterruptCallback_t);

DECLARE_INTERRUPT(0, 0)
// And so on...

然后在执行文件中interrupts.cpp:

#include <utility>

#include "interrupts.hpp"

static void apply_interrupt_callback(InterruptCallbackList_t const & cbList) {
    for (const auto & cb : cbList) {
        cb();
    }
}

#define IMPLEMENT_INTERRUPT(pin, mode) IMPLEMENT_INTERRUPT_(pin, mode)
#define IMPLEMENT_INTERRUPT_(pin, mode) \
    static InterruptCallbackList_t generated_interrupt_callbacks_##pin##_##mode ; \
    static void generated_interrupt_callback_##pin##_##mode() { \
        apply_interrupt_callback( generated_interrupt_callbacks_##pin##_##mode ); \
    } \
    void add_interrupt_callback_##pin##_##mode (InterruptCallback_t cb) { \
        ( generated_interrupt_callbacks_##pin##_##mode ).emplace_back(std::move(cb)); \
    } \
    static int generated_interrupt_registration_##pin##_##mode = ( \
        attachInterrupt(pin, generated_interrupt_callback_##pin##_##mode, mode), \
        0 \
    );

IMPLEMENT_INTERRUPT(0, 0)
// And so on...

这将为您提供的每个 pin/mode 组合生成一组函数和全局变量。 interrupts.hpp 提供的接口将公开 add_interrupt_callback_N_M,其中 NM 分别是引脚和模式常量。您可以将任何函数或仿函数(在 std::function<void()> 的帮助下传递给此函数,它会在中断发生时被调用。

请注意,此示例代码中没有互斥体。为了简单起见,我假设一个 single-threaded 环境,其中中断回调不能抢先发生。如果这些假设不成立,则需要更改代码。