使用引用捕获替代函数指针

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;
        }
    }
});

其中 Entity 只是一个可移动的点状物体。

我已经准备好了,然后我意识到不能将带有引用捕获的 lambda 做成函数指针(回想起来,原因是有道理的)。

我能找到的唯一替代方法是使用 std::/boost::function,但语法相当丑陋,而且显然它们带来了相当大的开销。

这个系统有什么好的替代品?我希望能够将某种接受字符的 "handler" 传递给 EventHandler,并且能够对某些外部范围产生副作用。

在下面的源代码中,LockedQueue 是一个使用 mutexes 实现线程安全的 FIFO 队列。

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 是 "broken" 因为我在函数内部加入,所以控制永远不会离开它。我需要想出一个解决方法。虽然没有改变问题,但代码在当前状态下不可测试。

The only alternative I could find was to use std::/boost::function, but the syntax is rather ugly, and apparently they come with a decent amount of overhead.

与内联函数相比,开销是不错的,但是 it's measured in nanoseconds。如果您每秒仅调用该函数 60 次,则开销是不可估量的。

也就是说,如果您需要能够随时更改事件处理程序,您唯一的选择是虚拟方法调用,具有类似的开销。本文对这些选择的性能影响进行了彻底探讨:Member Function Pointers and the Fastest Possible C++ Delegates.

如果您乐于将 EventHandler 对象限制为执行在编译时定义的单个代码块,请使用模板来存储编译器为 lambda 生成的类型的实例;这应该允许编译器执行更多优化,因为它可以确定正在调用什么代码。在这种情况下,KeyHandler 成为模板类型,并且可以使用 decltype 关键字找到 lambda 的类型:

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
    });
}

std::functionboost::function 考虑到在这种情况下您对它们的使用有多轻松,因此不会带来任何意义不大的开销。在确定所谓的缺点实际适用于您之前,您放弃了解决方案,从而犯了严重错误。

您当然也可以使用其他答案中描述的模板,但实际上没有必要。