我很久没有使用C ++了。在我遗忘的内容和C ++随着时间的推移所发生的变化之间,我实在不知所措,试图做一些在JavaScript或其他任何将函数作为对象的语言中都非常容易实现的事情,而不仅仅是简单指针。
我想我了解基本问题:类成员函数仅在内存中一次存在(每个类实例没有不同的副本)。函数知道“ this”的唯一方法是因为实例指针作为不可见的第一个参数传递给每个函数调用。一个普通的C风格的回调对传递该实例指针一无所知。
我需要一个新函数,该新函数以某种方式绑定到我的类实例,该函数知道如何将“ this”传递给成员函数。这就是我需要用作回调的功能。
但是我不确定如何动态创建这样的函数。我认为下面的代码处在正确的轨道上(除了强制转换指针类型外),但它确实让我感到困扰,因为似乎必须进行一些动态内存分配,如果是这样,跟踪该分配并稍后进行清理。
class SignalMonitor {
int dataPin;
unsigned short timings[RING_BUFFER_SIZE];
unsigned long lastSignalChange = 0;
int dataIndex = 0;
int syncCount = 0;
void signalHasChanged();
public:
SignalMonitor(int);
};
SignalMonitor::SignalMonitor(int dataPin) {
this->dataPin = dataPin;
function<void()> callback = bind(&SignalMonitor::signalHasChanged, this);
wiringPiISR(dataPin, INT_EDGE_BOTH, callback);
}
void SignalMonitor::signalHasChanged() {
unsigned long now = micros();
int duration = (int) min(now - this->lastSignalChange, 10000ul);
this->lastSignalChange = now;
cout << duration << '\n';
}
我觉得这很接近我想要的,但出现此错误:
acu-rite-433Mhz-reader.cpp:58:72: error: invalid cast from type ‘std::function<void()>’ to type ‘void*’
wiringPiISR(dataPin, INT_EDGE_BOTH, reinterpret_cast<void *>(callback));
^
这里是我试图将此回调传递给的函数的调用签名:
intconnectionPiISR(int引脚,int edgeType,void(*函数)(void))
我在搜索该主题时发现了许多类似的问题,但是它们要么与我要尝试的工作不完全相同,要么比我现在所拥有的C ++更加熟悉。 (关于函数指针类型,我只记得它们可以很快变得非常丑陋!)
[我试图使用lambda函数作为解决方案,但是这导致了某些东西是“临时的”错误(除了类型不匹配错误),我认为这意味着lambda函数的作用域是临时的。
这可能有助于考虑处理类似问题的传统方法。已经设计了其他API,其中void(*function)(void)
代替了wiringPiISR
,而期望使用函数void(*function)(void *)
。这允许使用
static void signalHasChanged(void *p) {
static_cast<SignalMonitor*>(p)->signalHasChanged();
}
这不是一个通用的解决方案,但是由于Raspberry Pi具有数量有限的GPIO引脚,并且回调功能的数量不能超过引脚的数量,因此您可能可以为每个引脚创建一个回调功能。然后,您需要一个全局数据结构,该结构将中断引脚映射到应向其发出信号的SignalMonitor实例。构造函数会将“ this”对象注册到特定的针脚,然后根据该针脚设置适当的回调函数。
回调函数将能够将pin参数传递给通用函数,该通用函数然后可以查找特定的SignalMonitor实例并调用类函数。
我不想为1000个引脚,1000个实例执行此操作,但是此hack应该适用于在Pi上运行的任何程序。
这不是理想的解决方案(我开始认为这里没有理想的解决方案),但是它在这种特殊情况下适用于我,因为在这种情况下,SignalMonitor
类的实例可能不会太多同时使用。
首先,我将signalHasChanged
类方法转换为以实例为参数的静态方法。 (通过进行一些繁琐的类型转换,我本可以将该方法保留为类方法,但这是不值得的。)
然后我制作了十个几乎完全相同的间接回调函数:
void smCallback0() { SignalMonitor::signalHasChanged(monitors[0]); }
void smCallback1() { SignalMonitor::signalHasChanged(monitors[1]); }
void smCallback2() { SignalMonitor::signalHasChanged(monitors[2]); }
void smCallback3() { SignalMonitor::signalHasChanged(monitors[3]); }
void smCallback4() { SignalMonitor::signalHasChanged(monitors[4]); }
void smCallback5() { SignalMonitor::signalHasChanged(monitors[5]); }
void smCallback6() { SignalMonitor::signalHasChanged(monitors[6]); }
void smCallback7() { SignalMonitor::signalHasChanged(monitors[7]); }
void smCallback8() { SignalMonitor::signalHasChanged(monitors[8]); }
void smCallback9() { SignalMonitor::signalHasChanged(monitors[9]); }
然后,我将所有这些功能粘贴到数组中:
void (*_smCallbacks[MAX_MONITORS])() = {
smCallback0, smCallback1, smCallback2, smCallback3, smCallback4,
smCallback5, smCallback6, smCallback7, smCallback8, smCallback9
};
伴随monitors
数组,它是SingleHandler
指针的数组,这给了我十个可用的回调槽。 (_smCallbacks
被复制到smCallbacks
中,以解决前向参考问题。)
init
的SignalMonitor
方法仅搜索可用的插槽,插入自身,然后设置回调:
void SignalMonitor::init() {
for (int i = 0; i < MAX_MONITORS; ++i) {
if (monitors[i] == NULL) {
callbackIndex = i;
monitors[i] = this;
break;
}
}
if (callbackIndex < 0)
throw "Maximum number of SignalMonitor instances reached";
wiringPiISR(dataPin, INT_EDGE_BOTH, smCallbacks[callbackIndex]);
}
还有一个析构函数可以释放回调插槽:
SignalMonitor::~SignalMonitor() {
if (callbackIndex >= 0)
monitors[callbackIndex] = NULL;
}