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

问题描述

我想让我的班级采用一个函数“回调存根”,在存根之前和之后添加一些处理的东西,然后将整个事情作为回调传递给中断。不同的存根可以附加到类的不同实例。

我收到了如下评论中所示的错误。它的工作原理是获取一个函数指针,将其存储为一个属性,然后将其传递给 attachInterrupt 函数。但是,添加小序曲是行不通的。

setInterrupt()中,我得到指向绑定函数的指针只能用于调用函数C/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() {}

解决方法

在 myCallBack() 中,我得到标识符 "myCallBackStub" is undefinedC/C++(20)

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

void myCallBack() {

但你应该:

void myClass::myCallBack() {

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

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

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

值得注意的是,您不能将其转换为函数指针。这是因为非静态成员函数需要一个实例来调用函数,而自由函数指针(attachInterrupt 接受的)没有额外的机制来传达这种状态。即使您对指向成员函数的指针 (&myClass::myCallBack) 使用了正确的语法,这仍然不起作用,因为您无法在指向函数的指针和指向成员函数的指针之间进行转换。

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


由于 attachInterrupt() 状态无论如何都是全局的,如果使您的机制全局化,您不会获得很多好处。您遇到的主要挑战是回调函数不接受任何参数,因此您甚至无法判断使用的是哪个引脚/模式。

要解决这个问题,您需要为每个引脚/模式组合使用不同的非成员回调函数,并且必须在编译时提前知道这些,因为您(大概)不想要进入运行时发射蹦床的复杂性。

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

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

这是此概念的示例实现,可能需要进行调整才能在生产中使用。您可以使用以下内容创建一个类似于 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...

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

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