替代具有引用捕获的函数指针
Alternative to a function pointer with reference capture
我正在编写一个事件处理程序,它监听按键,然后对任何按键调用一个处理程序。我的目标是允许这样的东西:
Entity player(0, 0);
EventHandler eh([&](char c) {
switch (c) {
case 'W': {
player.moveBy(0,-1);
break;
}
case 'S': {
player.moveBy(0, 1);
break;
}
case 'A': {
player.moveBy(-1, 0);
break;
}
case 'D': {
player.moveBy(1, 0);
break;
}
}
});
其中CCD_ 1只是可移动的点状对象。
我已经做好了准备,然后我意识到带有引用捕获的lambda不能成为函数指针(回想起来,原因是有道理的)。
我能找到的唯一选择是使用std::
/boost::function
,但语法相当难看,而且显然它们会带来相当大的开销。
这个系统有什么好的替代方案?我希望能够将某种"处理程序"传递给接受字符的EventHandler
,并能够在某些外部作用域上执行副作用。
在下面的源代码中,LockedQueue
是一个FIFO队列,它使用mutex
es实现了线程安全
EventHandler.h:
#ifndef EVENT_HANDLER_H
#define EVENT_HANDLER_H
#include <vector>
#include <atomic>
#include "LockedQueue.h"
class EventHandler {
typedef void(*KeyHandler)(char);
std::atomic<bool> listenOnKeys = false;
std::vector<char> keysToCheck;
LockedQueue<char> pressedKeys;
KeyHandler keyHandler = nullptr;
void updatePressedKeys();
void actOnPressedKeys();
public:
EventHandler();
EventHandler(KeyHandler);
~EventHandler();
void setKeyHandler(KeyHandler);
void setKeysToListenOn(std::vector<char>);
void listenForPresses(int loopMSDelay = 100);
void stopListening();
};
#endif
EventHandler.cpp:
#include "EventHandler.h"
#include <windows.h>
#include <WinUser.h>
#include <thread>
#include <stdexcept>
EventHandler::EventHandler() {
}
EventHandler::EventHandler(KeyHandler handler) {
keyHandler = handler;
}
EventHandler::~EventHandler() {
stopListening();
}
void EventHandler::updatePressedKeys() {
for (char key : keysToCheck) {
if (GetAsyncKeyState(key)) {
pressedKeys.push(key);
}
}
}
void EventHandler::actOnPressedKeys() {
while (!pressedKeys.empty()) {
//Blocking if the queue is empty
//We're making sure ahead of time though that it's not
keyHandler(pressedKeys.waitThenPop());
}
}
void EventHandler::setKeyHandler(KeyHandler handler) {
keyHandler = handler;
}
void EventHandler::setKeysToListenOn(std::vector<char> newListenKeys) {
if (listenOnKeys) {
throw std::runtime_error::runtime_error(
"Cannot change the listened-on keys while listening"
);
//This could be changed to killing the thread by setting
// listenOnKeys to false, changing the keys, then restarting
// the listening thread. I can't see that being necessary though.
}
//To-Do:
//Make sure all the keys are in upper-case so they're
// compatible with GetAsyncKeyState
keysToCheck = newListenKeys;
}
void EventHandler::listenForPresses(int loopMSDelay) {
listenOnKeys = true;
std::thread t([&]{
do {
updatePressedKeys();
actOnPressedKeys();
std::this_thread::sleep_for(std::chrono::milliseconds(loopMSDelay));
} while (listenOnKeys);
});
t.join();
}
void EventHandler::stopListening() {
listenOnKeys = false;
}
编辑:
哇。注意,listenForPresses
是"坏掉的",因为我在函数内部加入,所以控制永远不会离开它。我需要找到一个解决方法。虽然这并没有改变问题,但代码在当前状态下是不可测试的。
我能找到的唯一替代方法是使用std::/boost::函数,但语法相当难看,而且显然它们会带来相当大的开销。
与内联函数相比,开销是不错的,但它是以纳秒为单位测量的。如果你每秒只调用函数60次,那么开销是不可估量的。
也就是说,如果您需要能够在任何时候更改事件处理程序,那么您唯一的选择就是具有类似开销的虚拟方法调用。本文将深入探讨这些选择对性能的影响:成员函数指针和可能的最快C++委托。
如果您愿意将EventHandler对象限制为执行编译时定义的单个代码块,请使用模板为lambda存储编译器生成的类型的实例;这应该允许编译器执行更多的优化,因为它可以确定调用了什么代码。在这种情况下,KeyHandler
变成了一个模板类型,lambda的类型可以用decltype
关键字找到:
template <class KeyHandler>
class EventHandler {
// elided
}
void EventLoopDecltype() {
Entity player(0, 0);
auto myEventHandler = [&](char ch) { /* elided */ };
EventHandler<decltype(myEventHandler)> eh(myEventHandler);
}
或者(更方便地,对于调用者)推断为模板函数的参数:
template <class KeyHandler>
EventHandler<KeyHandler> MakeEventHandler(KeyHandler handler) {
return EventHandler<KeyHandler>(handler);
}
void EventLoopInferred() {
Entity player(0, 0);
auto eh = MakeEventHandler([&](char c) {
// elided
});
}
Entity
0和boost::function
没有任何有意义的开销,考虑到在这种情况下您对它们的使用会有多轻。你犯了一个严重的错误,在确定所谓的缺点实际上适用于你之前就放弃了解决方案。
当然,你也可以使用另一个答案中描述的模板,但实际上没有必要
- QMetaObject invokeMethod的基于函数指针的语法
- C++-试图将函数指针推回到另一个CPP文件中的矢量时出错
- c++r值引用应用于函数指针
- 模板函数指针和lambda
- 是否可以将llvm::FunctionType转换为C/C++原始函数指针
- 带有类的函数指针
- () 函子后面的括号,而不是函数指针?
- 全局作用域中函数指针的赋值
- 使用"Task"函数指针队列定义作业管理器
- 将成员函数指针作为参数传递给模板方法
- 如何创建对象函数指针C++映射?
- 匹配函数指针作为模板参数?
- 通过函数指针定义类范围之外的方法
- 存储在类中的函数指针
- C++从函数指针数组调用函数
- 将返回值存储在函数指针数组的指针中是如何工作的?
- 整数键映射到头文件中的成员函数指针
- 从类成员函数到类 C 函数指针的转换
- 如何将内联匿名函数分配给C++函数指针
- 将字符缓冲区强制转换为函数指针