Регистрация функции-члена класса в качестве обратного вызова функции с использованием std::bind

Я пытаюсь зарегистрировать функцию-член класса как (обычную) функцию обратного вызова. Если я что-то не так понял, это должно быть возможно с помощью std::bind (с использованием С++ 11). Я делаю это следующим образом:

std::function<void (GLFWwindow*, unsigned int)> cb = std::bind(&InputManager::charInputCallback, this, std::placeholders::_1, std::placeholders::_2);

Моя функция обратного вызова определяется следующим образом:

void InputManager::charInputCallback(GLFWwindow* window, unsigned int key)

Я могу протестировать cb сразу после создания, используя случайные данные:

cb(NULL, 0x62);

Я могу подтвердить, что эти данные правильно отправлены в функцию обратного вызова, распечатав ее на терминале.

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

glfwSetCharCallback(window, (GLFWcharfun) &cb);

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

Является ли std::bind не тем, что я ищу? Я использую его неправильно?

Изменить: я не думаю, что этот вопрос является дубликатом Как я могу передать функцию-член класса в качестве обратного вызова? как она была идентифицирована как. Пока мы решаем одну и ту же проблему, я спрашиваю об этом конкретном решении с использованием std::bind, которое только упоминается, но никогда не объясняется в одном из ответов на другой вопрос.


person Sven G    schedule 12.09.2016    source источник
comment
Возможный дубликат Как я могу передать член класса функционировать как обратный вызов?   -  person Ari0nhh    schedule 12.09.2016
comment
C++ не поддерживает сборку мусора. this не остается волшебным образом только потому, что вы привязаны к обратному вызову. Если вы неправильно управляли временем жизни, ваш InputManager мог быть уничтожен к тому времени, когда GLFW вызовет обратный вызов. Это вызовет SegFault. Однако у SegFaults могут быть и другие причины — трудно сказать без минимального примера.   -  person MSalters    schedule 12.09.2016
comment
@MSalters: Спасибо, что указали на это, но мой объект существует на протяжении всего приложения.   -  person Sven G    schedule 12.09.2016


Ответы (1)


Вот объявление функции:

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                    GLFWcharfun     cbfun 
                                )

Где GLFWcharfun определяется как typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int)

Здесь существует очевидная проблема, заключающаяся в том, что у вас нет возможности передать объект «контекст», который автоматически сопоставит обратный вызов с экземпляром InputManager. Таким образом, вам придется выполнять сопоставление вручную, используя единственную доступную вам клавишу — указатель окна.

Вот одна из стратегий...

#include <map>
#include <mutex>

struct GLFWwindow {};
typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int);

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                 GLFWcharfun    cbfun
                                 );


struct InputManager;

struct WindowToInputManager
{
    struct impl
    {
        void associate(GLFWwindow* window, InputManager* manager)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_[window] = manager;
        }

        void disassociate(GLFWwindow* window, InputManager* manager)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_.erase(window);
        }

        InputManager* find(GLFWwindow* window) const
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            auto i = mapping_.find(window);
            if (i == mapping_.end())
                return nullptr;
            else
                return i->second;
        }

        mutable std::mutex mutex_;
        std::map<GLFWwindow*, InputManager*> mapping_;
    };

    static impl& get_impl() {
        static impl i {};
        return i;
    }

    void associate(GLFWwindow* window, InputManager* manager)
    {
        get_impl().associate(window, manager);
    }

    void disassociate(GLFWwindow* window, InputManager* manager)
    {
        get_impl().disassociate(window, manager);
    }

    InputManager* find(GLFWwindow* window)
    {
        return get_impl().find(window);
    }

};

struct InputManager
{
    void init()
    {
        // how to set up the callback?

        // first, associate the window with this input manager
        callback_mapper_.associate(window_, this);

        // now use a proxy as the callback
        glfwSetCharCallback(window_, &InputManager::handleCharCallback);

    }

    static void handleCharCallback(GLFWwindow *     window,
                           unsigned int ch)
    {
        // proxy locates the handler
        if(auto self = callback_mapper_.find(window))
        {
            self->charInputCallback(window, ch);
        }

    }

    void charInputCallback(GLFWwindow *     window,
                           int ch)
    {
        // do something here
    }


    GLFWwindow* window_;
    static WindowToInputManager callback_mapper_;    
};

Или, если вы предпочитаете закрытие:

#include <map>
#include <mutex>

struct GLFWwindow {};
typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int);

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                 GLFWcharfun    cbfun
                                 );


struct InputManager;

struct WindowToInputManager
{
    using sig_type = void (GLFWwindow *, unsigned int);
    using func_type = std::function<sig_type>;

    struct impl
    {
        void associate(GLFWwindow* window, func_type func)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_[window] = std::move(func);
        }

        void disassociate(GLFWwindow* window)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_.erase(window);
        }

        const func_type* find(GLFWwindow* window) const
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            auto i = mapping_.find(window);
            if (i == mapping_.end())
                return nullptr;
            else
                return std::addressof(i->second);
        }

        mutable std::mutex mutex_;
        std::map<GLFWwindow*, func_type> mapping_;
    };

    static impl& get_impl() {
        static impl i {};
        return i;
    }

    template<class F>
    void associate(GLFWwindow* window, F&& f)
    {
        get_impl().associate(window, std::forward<F>(f));
        glfwSetCharCallback(window, &WindowToInputManager::handleCharCallback);
    }

    void disassociate(GLFWwindow* window)
    {
        // call whatever is the reverse of glfwSetCharCallback here
        //

        // then remove from the map
        get_impl().disassociate(window);
    }

    const func_type* find(GLFWwindow* window)
    {
        return get_impl().find(window);
    }

    static void handleCharCallback(GLFWwindow* w, unsigned int ch)
    {
        auto f = get_impl().find(w);
        // note - possible race here if handler calls disasociate. better to return a copy of the function?
        if (f) {
            (*f)(w, ch);
        }
    }

};

struct InputManager
{
    void init()
    {
        callback_mapper_.associate(window_, [this](auto* window, int ch) { this->charInputCallback(window, ch); });

    }

    void charInputCallback(GLFWwindow * window,
                           int ch)
    {
        // do something here
    }


    GLFWwindow* window_;
    WindowToInputManager callback_mapper_;

};
person Richard Hodges    schedule 12.09.2016
comment
Я вижу, как это может работать. Конечно, мне придется получить доступ к параметру окна, используя this, из-за того, как параметры помещаются в стек, но мне определенно нравится идея карты. Я попробую, прежде чем отмечу этот ответ как принятый. В конце концов, я надеялся на решение std::bind. - person Sven G; 12.09.2016
comment
@SvenG второе решение - это решение для привязки (через неизбежную карту). Но я использовал лямбду вместо привязки, потому что std::bind — зловещий анахронизм, который был необходим до того, как у нас появились лямбды. Сегодня этого следует избегать. - person Richard Hodges; 12.09.2016
comment
@SvenG Я только что заметил, что все функции glew должны вызываться из основного потока, и обратные вызовы также будут поступать в этот поток. В этом случае, вероятно, нет необходимости в мьютексе. - person Richard Hodges; 12.09.2016