【问题标题】:Function Pointer as Function Parameter函数指针作为函数参数
【发布时间】:2013-11-10 12:17:25
【问题描述】:

我一直在编写一个实用程序来作为 GLFW 键输入的包装器。

我希望有一组函数,使我能够轻松地传递键代码以及我希望在按下或释放键时触发的函数。

我认为这里的一切都是正确的,但我试图在我的 Game 类中使用它,但出现以下错误:

game.cpp(27): error C2664: 'void KeyManager::press(int,void *)' : 无法将参数 2 从 'void (__thiscall Game::* )(void)' 转换为 'void *' 1> 没有可以进行这种转换的上下文

游戏.cpp:

void Game::testKey()
{
    std::cout << "Key Up Pressed" << std::endl;
}

void Game::init()
{
    //  KEY MANAGER:
    KeyManager::initialize();
    KeyManager::press(GLFW_KEY_UP, &Game::testKey);

KeyManager.cpp:

#include <map>
#include <vector>

#include "global.h"
#include "key_manager.h"

namespace KeyManager
{
    std::map<int, int>                      keyAction;
    std::map<int, std::vector<void(*)()>>   pressFunctions;
    std::map<int, std::vector<void(*)()>>   releaseFunctions;

    static void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
    {
        keyAction[key] = action;

        if (keyAction[key] == GLFW_PRESS)
        {
            for (int i = 0; i != pressFunctions[key].size(); ++i)
            {
                (*pressFunctions[key].at(i))();
            }
        }

        if (keyAction[key] == GLFW_RELEASE)
        {
            for (int i = 0; i != releaseFunctions[key].size(); ++i)
            {
                (*releaseFunctions[key].at(i))();
            }
        }
    }

    void initialize()
    {
        glfwSetKeyCallback(window, key_callback);
    }

    void press(int keyCode, void(*listener)())
    {
        pressFunctions[keyCode].push_back(listener);
    }

    void release(int keyCode, void(*listener)())
    {
        releaseFunctions[keyCode].push_back(listener);
    }

    bool held(int keyCode)
    {
        if (keyAction[keyCode] == GLFW_PRESS || keyAction[keyCode] == GLFW_REPEAT)
        {
            return true;
        } else {
            return false;
        }
    }
}

【问题讨论】:

    标签: c++ vector map key glfw


    【解决方案1】:

    当您获取非static 成员函数的地址时,您不会得到一个“正常”的函数指针,而是一个指向成员函数的指针:成员函数不仅将显式指定的参数作为参数,但它们也将指向对象的隐式指针(变成this)作为参数。所以你的Game::testKey的类型不是void(*)()而是void (Game::*)(),说明调用这个函数的时候还需要传入一个Game对象。

    如果您可以控制被调用函数的类型,您最好使用函数对象的模板参数,或者,如果您需要存储多个此类对象,则使用具有合适类型 @ 的 std::function&lt;Signature&gt; 987654328@,在您的情况下可能是void():这些对象可以存储一个对象,您可以传入所需的对象,例如:

    std::function<void()> fun(std::bind(&Game::testKey, this));
    

    std::bind() 是一个工厂函数,它以一个函数作为它的第一个参数和一组参数,指定函数在调用时应获取的参数。在上面的示例中,Game::testKey 函数将第一个参数绑定到 this(当然,您可以使用任何指向 Game 对象的指针)。

    当你真的不需要一个对象来发送函数时,你也可以创建函数static,这会产生一个不带隐式对象的函数。但是,通常该对象实际上是必需的,并且仅创建函数 static 并不能提供足够的上下文。

    假设上下文需要Game 对象,KeyManager 将使用std::function&lt;void(char)&gt; 来报告关键事件(我还添加了一个char 参数来获取使用了哪个键的anof):

    class KeyManager
    {
        std::vector<std::function<void(char)>> handlers;
    public:
        void addHandler(std::function<void(char)> h) {
            handlers.push_back(h);
        }
        //...
    };
    

    稍后,当您有合适的Game 对象时,您可以添加一个处理函数:

    KeyManager m;
    Game           g;
    m.addHandler(std::bind(&Game::keyDown, &game));
    

    请注意,Game::keyDown 实际上使用了Gane 对象并且没有额外的参数:这没关系,因为从std::bind() 返回的函数对象将忽略额外的参数。

    【讨论】:

    • 如果我每次想向 KeyManager 添加侦听器函数时都必须创建一个模板/签名,这有点不合时宜。有没有办法让它像我展示的那样工作 KeyManager::press(GLFW_KEY_UP, &amp;Game::testKey); 可能通过更改 KeyManager 函数?
    • @SyntheCypher:是的,当仅使用一个函数对象时,使用模板化函数对象会非常有效(因为它可以内联)。因此,您可以使用 std::function&lt;Signature&gt; 和固定的 Signature(例如,void())。正如答案中已经写的那样。
    • 很抱歉,我仍然坚持这一点,您能否写一个更完整的示例,说明如何将其合并到 KeyManager.cpp 和 Game.cpp 中?
    • 我遇到了一个问题,它无法将 std::bind 解析为 std::function 这是 addHandler 参数。