Указатель функции как параметр функции

Я писал утилиту для работы в качестве оболочки для ввода ключа GLFW.

Я хотел иметь набор функций, которые позволили бы мне легко передавать код клавиши и функцию, которую я хотел запускать при нажатии или отпускании клавиши.

Я думаю, что здесь все правильно, но я пытаюсь использовать его в своем классе Game и получаю следующую ошибку:

game.cpp (27): ошибка C2664: 'void KeyManager :: press (int, void *)': невозможно преобразовать аргумент 2 из 'void (__thiscall Game :: *) (void)' в 'void *' 1> Там нет контекста, в котором это преобразование возможно

Game.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;
        }
    }
}

person Joshua Barnett    schedule 10.11.2013    source источник


Ответы (1)


Когда вы берете адрес функции-члена, не являющейся static, вы получаете не «нормальный» указатель на функцию, а скорее указатель на функцию-член: функции-члены не просто принимают явно указанные аргументы в качестве параметров, но они также принимают неявный указатель на объект (который становится this) в качестве параметра. Таким образом, тип вашего Game::testKey - не void(*)(), а скорее void (Game::*)(), что указывает на то, что вам все равно нужно передать Game объекты при вызове этой функции.

Если вы можете контролировать тип вызываемых функций, лучше всего использовать либо аргумент шаблона для объекта функции, либо, если вам нужно сохранить несколько таких объектов, std::function<Signature> с подходящим типом Signature, в вашем случае, вероятно, void(): эти объекты могут хранить объект, и вы можете передать желаемый объект, например:

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

std::bind() - это заводская функция, которая принимает функцию в качестве первого параметра и набор аргументов, определяющих аргументы, которые функция должна получать при вызове. В приведенном выше примере функция Game::testKey получает первый аргумент, привязанный к this (вы, конечно, можете использовать любой указатель на объект Game, который хотите использовать).

Если вам действительно не нужен объект для отправки функции, вы также можете создать функцию static, которая приводит к функции, которая не принимает неявный объект. Однако часто объект действительно требуется, и простое выполнение функции static не обеспечивает достаточного контекста.

Предполагая, что объект Game необходим для контекста, KeyManager будет использовать std::function<void(char)> для сообщения о ключевых событиях (я также добавил параметр char, чтобы узнать, какой ключ был использован):

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(), просто проигнорирует дополнительные аргументы.

person Dietmar Kühl    schedule 10.11.2013
comment
Это своего рода поражение, если мне приходилось создавать шаблон / подпись каждый раз, когда я хотел добавить функцию прослушивателя в KeyManager. Есть ли способ заставить его работать так, как я показал KeyManager::press(GLFW_KEY_UP, &Game::testKey); Возможно, изменив функции KeyManager? - person Joshua Barnett; 10.11.2013
comment
@SyntheCypher: Да, использование шаблонного функционального объекта работает очень хорошо и эффективно (поскольку он может быть встроен) при использовании только одного функционального объекта. Таким образом, вместо этого вы должны использовать std::function<Signature> с фиксированным Signature (например, void()). Как уже написано в ответе. - person Dietmar Kühl; 10.11.2013
comment
Извините, я все еще застрял на этом, не могли бы вы написать более полный пример того, как это будет включено в KeyManager.cpp и Game.cpp? - person Joshua Barnett; 10.11.2013
comment
У меня проблема с невозможностью синтаксического анализа std :: bind с std :: function, который является аргументом addHandler. - person Joshua Barnett; 12.11.2013