替代带参考捕获的函数指针

时间:2015-04-18 19:13:12

标签: c++ lambda

我正在编写一个侦听按键的事件处理程序,然后在任何按下的键上调用处理程序。我的目标是允许这样的事情:

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,但语法相当丑陋,显然它们带来了相当大的开销。

这个系统有什么好的选择?我希望能够通过某种"处理程序"接受一个角色的EventHandler,并且能够对某些外部范围产生副作用。

在以下来源中,LockedQueue是使用mutex es使线程安全的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已被打破&#34;因为我在函数内部加入,所以控制永远不会离开它。我需要找出一个解决方法。虽然没有改变这个问题,但代码在其当前状态下是不可测试的。

2 个答案:

答案 0 :(得分:1)

  

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

与inlinable函数相比,开销是合理的,但是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
    });
}

答案 1 :(得分:1)

std::functionboost::function没有带来任何远程有意义的开销,考虑到在这种情况下你对它们的使用有多么轻松。在确定所声称的缺点实际适用于您之前,您已通过放弃解决方案而犯了一个严重错误。

你当然也可以使用另一个答案中描述的模板,但实际上没有必要。