使用std::bind将类成员函数注册为函数的回调

Registering a class member function as a callback to a function using std::bind

本文关键字:函数 注册 回调 std bind 使用 成员      更新时间:2023-10-16

我试图注册一个类成员函数作为(常规)回调函数。除非我误解了什么,否则使用std::bind(使用c++ 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的特殊解决方案,在另一个问题的一个答案中只提到了它,但从未解释过它。

函数声明如下:

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                    GLFWcharfun     cbfun 
                                )

其中GLFWcharfun定义为typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int)

这里有一个明显的问题,你没有机会传递一个'context'对象,它会自动将回调映射回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_;
};