C++ 指向作为类成员的函数 - glfw setKeycallback
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7676971/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pointing to a function that is a class member - glfw setKeycallback
提问by viraj
I'm writing a glfw app, in which I've wrapped the function callse into a simple class. Im having trouble setting the key callback. My class is defined as:
我正在编写一个 glfw 应用程序,其中我将函数调用包装到一个简单的类中。我在设置键回调时遇到问题。我的班级定义为:
class GAME
{
private:
bool running;
public:
GAME();
int execute();
void events(int, int);
int loop();
int render();
};
The execute function is:
执行函数是:
int GAME::execute()
{
glfwOpenWindow(640, 320, 8, 8, 8, 8, 0, 0, GLFW_WINDOW);
glfwSetWindowTitle("Viraj");
glfwSetKeyCallback(events);
running = true;
while(glfwGetWindowParam(GLFW_OPENED))
{
glfwPollEvents();
loop();
render();
}
return 0;
}
Compiling the following code on Visual Studio 2010, gives the error:
在 Visual Studio 2010 上编译以下代码,会出现错误:
error C3867: 'GAME::events': function call missing argument list; use '&GAME::events' to create a pointer to member
error C3867: 'GAME::events': function call missing argument list; use '&GAME::events' to create a pointer to member
Using &GAME::events gives:
使用 &GAME::events 给出:
error C2664: 'glfwSetKeyCallback' : cannot convert parameter 1 from 'void (__thiscall GAME::* )(int,int)' to 'GLFWkeyfun'
1> There is no context in which this conversion is possible
error C2664: 'glfwSetKeyCallback' : cannot convert parameter 1 from 'void (__thiscall GAME::* )(int,int)' to 'GLFWkeyfun'
1> There is no context in which this conversion is possible
采纳答案by RushPL
There is a C++ syntax for pointing to class member methods but you cannot pass them to a C style API. C understands function calls and every non-static object method, taking your events
as an example, looks like this thinking in C terms: void events(void* this, int, int);
meaning that every method apart from the standard arguments also gets a this
pointer silently passed.
有一个指向类成员方法的 C++ 语法,但您不能将它们传递给 C 风格的 API。C 理解函数调用和每个非静态对象方法,以您events
为例,在 C 术语中看起来像这样的想法:void events(void* this, int, int);
意味着除了标准参数之外的每个方法也得到一个this
静默传递的指针。
To make your events
C compatible make it static void events(int, int);
. This way it will follow the C calling semantics - it will not require a this
pointer getting passed. You have to also somehow pass your object to this callback in some other manner (if you need this object's data in the callback).
为了使您的events
C 兼容 make it static void events(int, int);
。这样它将遵循 C 调用语义 - 它不需要this
传递指针。您还必须以某种其他方式将您的对象以某种方式传递给此回调(如果您在回调中需要此对象的数据)。
回答by N0vember
The code examples provided in the other answers don't describe how to redirect your callback to a per-object member function, with possibly any number of objects. Making your class a singleton will constrain your design and will not scale to multiple glfw windows.
其他答案中提供的代码示例没有描述如何将回调重定向到每个对象的成员函数,可能有任意数量的对象。使您的类成为单例将限制您的设计,并且不会扩展到多个 glfw 窗口。
The scalable solution is to set the glfw window user pointer to your object and then fetch it in the callback, and call the member function :
可扩展的解决方案是将 glfw 窗口用户指针设置为您的对象,然后在回调中获取它,并调用成员函数:
class MyGlWindow
{
public:
void mouseButtonPressed();
};
void makeWindow()
{
GLFWwindow* glfwWindow;
MyGlWindow* myWindow;
/* ... Initialize everything here ... */
glfwSetWindowUserPointer(glfwWindow, myWindow);
auto func = [](GLFWwindow* w, int, int, int)
{
static_cast<MyGlWindow*>(glfwGetWindowUserPointer(w))->mouseButtonPressed( /* ... */ );
}
glfwSetMouseButtonCallback(glfwWindow, func);
}
This solution is shorter and will work for any number of windows.
此解决方案更短,适用于任意数量的窗口。
回答by Mobiletainment
I also ran into this problem with another glfw callback function, but I didn't want to declare my class method as static
, because I needed to access the member variables within. So I tried std::function
and std::bind
for giving me the ability to bind an instance method as the callback function, but unfortunately it's not an option when working with C callbacks.
我也用另一个 glfw 回调函数遇到了这个问题,但我不想将我的类方法声明为static
,因为我需要访问其中的成员变量。于是,我就std::function
和std::bind
给我结合实例方法的回调函数的能力,但以C回调工作时,不幸的是它不是一个选项。
The answer to this problem is also stated in the GLFW FAQ "How do I use C++ methods as callbacks":
这个问题的答案也在 GLFW 常见问题解答“我如何使用 C++ 方法作为回调”中给出:
You cannot use regular methods as callbacks, as GLFW is a C library and doesn't know about objects and this pointers. If you wish to receive callbacks to a C++ object, use static methods or regular functions as callbacks, store the pointer to the object you wish to call in some location reachable from the callbacks and use it to call methods on your object.
您不能使用常规方法作为回调,因为 GLFW 是一个 C 库并且不知道对象和 this 指针。如果您希望接收对 C++ 对象的回调,请使用静态方法或常规函数作为回调,将指向您希望调用的对象的指针存储在可从回调到达的某个位置,并使用它来调用对象上的方法。
However, this encouraged me to apply the Singleton pattern for my callback class and integrate it as following:
然而,这鼓励我为我的回调类应用单例模式并将其集成如下:
- the callback method of my class is still static, so it can be specified/used as glfw callback
- this static callback method makes use of the singleton and passes the callback parameters to an instance method
- this instance method actually handles the callback parameters, with the benefit of being able to access the member variables
- 我的类的回调方法仍然是静态的,因此可以指定/用作 glfw 回调
- 此静态回调方法利用单例并将回调参数传递给实例方法
- 这个实例方法实际上处理回调参数,好处是能够访问成员变量
This is what it looks like:
这是它的样子:
// Input.h (the actual callback class for glfwSetMouseButtonCallback)
class Input
{
public:
static Input& getInstance() // Singleton is accessed via getInstance()
{
static Input instance; // lazy singleton, instantiated on first use
return instance;
}
static void mouseButtonCallback(int key, int action) // this method is specified as glfw callback
{
//here we access the instance via the singleton pattern and forward the callback to the instance method
getInstance().mouseButtonCallbackImpl(key, action);
}
void mouseButtonCallbackImpl(int key, int action) //this is the actual implementation of the callback method
{
//the callback is handled in this instance method
//... [CODE here]
}
private:
Input(void) // private constructor necessary to allow only 1 instance
{
}
Input(Input const&); // prevent copies
void operator=(Input const&); // prevent assignments
};
and in my main.cpp:
在我的 main.cpp 中:
Input &hexmap = Input::getInstance(); // initialize the singleton
//The glfw callback is set up as follows:
glfwSetMouseButtonCallback( &Input::mouseButtonCallback); // specifying the static callback method, which internally forwards it to the instance method
回答by BIC
I had the same problem and after reading this thread I came up with a similar solution. I think it is a bit cleaner this way. It's based on static function but it is nested inside the class where we set all things.
我遇到了同样的问题,在阅读此线程后,我想出了一个类似的解决方案。我认为这种方式更干净一些。它基于静态函数,但它嵌套在我们设置所有内容的类中。
Header looks like this:
标题看起来像这样:
class Application
{
public:
...
private:
...
void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
...
class GLFWCallbackWrapper
{
public:
GLFWCallbackWrapper() = delete;
GLFWCallbackWrapper(const GLFWCallbackWrapper&) = delete;
GLFWCallbackWrapper(GLFWCallbackWrapper&&) = delete;
~GLFWCallbackWrapper() = delete;
static void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
static void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
static void SetApplication(Application *application);
private:
static Application* s_application;
};
};
And the source code:
和源代码:
void Application::GLFWCallbackWrapper::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
s_application->MousePositionCallback(window, positionX, positionY);
}
void Application::GLFWCallbackWrapper::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
s_application->KeyboardCallback(window, key, scancode, action, mods);
}
void Application::GLFWCallbackWrapper::SetApplication(Application* application)
{
GLFWCallbackWrapper::s_application = application;
}
Application* Application::GLFWCallbackWrapper::s_application = nullptr;
void Application::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
...
}
void Application::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
...
}
void Application::SetCallbackFunctions()
{
GLFWCallbackWrapper::SetApplication(this);
glfwSetCursorPosCallback(m_window, GLFWCallbackWrapper::MousePositionCallback);
glfwSetKeyCallback(m_window, GLFWCallbackWrapper::KeyboardCallback);
}
回答by FrogTheFrog
Inspired by N0vember's answer, I present you even more generic and dynamic solution:
受到 N0vember 回答的启发,我向您展示了更通用和动态的解决方案:
class MyGlWindow {
public:
std::function<void(MyGlWindow*)> onClose;
std::function<void(MyGlWindow*, int, int, int)> onMouseClick = [](auto self, int, int, int) { /*some default behavior*/ };
};
void makeWindow() {
GLFWwindow* glfwWindow;
MyGlWindow* myWindow;
/* ... Initialize everything here ... */
glfwSetWindowUserPointer(glfwWindow, myWindow);
#define genericCallback(functionName)\
[](GLFWwindow* window, auto... args) {\
auto pointer = static_cast<MyGlWindow*>(glfwGetWindowUserPointer(window));\
if (pointer->functionName) pointer->functionName(pointer, args...);\
}
glfwSetWindowCloseCallback(glfwWindow, genericCallback(onClose));
glfwSetMouseButtonCallback(glfwWindow, genericCallback(onMouseClick));
myWindow->onMouseClick = [](auto self, int, int, int) {
std::cout << "I'm such a rebel" << std::endl;
self->onClose = [](auto self) {
std::cout << "I'm such a rebellion" << std::endl;
};
};
}
回答by SanD
In the header file make the events(int, int) to a static method. That solved the issue for me.
在头文件中,将 events(int, int) 设为静态方法。那为我解决了这个问题。
class GAME
{
private:
bool running;
public:
GAME();
int execute();
static void events(int, int); //Changed here to static void
int loop();
int render();
};
回答by user2113258
This is a useful discussion of possible solutions that helped me with the same problem, and I'm adding my solution in case it proves useful.
这是对帮助我解决相同问题的可能解决方案的有用讨论,如果证明有用,我将添加我的解决方案。
Problem Statement
问题陈述
My scenario is more general than the ones addressed by BIC, L.Senionis, and N0vember. In particular, my use case requires:
我的场景比 BIC、L.Senionis 和 N0vember 解决的场景更普遍。特别是,我的用例需要:
- Generally, instance's data must be accessible to the callback
- Many applications can be created using a common set of response handlers
- In an application, any number of windows may be created
- The set of callbacks attached to each window should be mixed and matched from a certain library of possible responders.
- 通常,实例的数据必须可以被回调访问
- 可以使用一组通用的响应处理程序创建许多应用程序
- 在一个应用程序中,可以创建任意数量的窗口
- 附加到每个窗口的回调集应该从某个可能的响应者库中混合和匹配。
Proposed Solution Usage
建议的解决方案用途
The simple singleton design no longer solves the problem. Instead, I provide a GLFWResponder
superclass that handles all of the setup complexity. In order to use the class and attach response to a window, here is what is required.
简单的单例设计不再能解决问题。相反,我提供了一个GLFWResponder
处理所有设置复杂性的超类。为了使用该类并将响应附加到窗口,这是必需的。
// Implement custom responder
class MyResponder : public GLFWResponder {
public:
virtual void cursor_position_callback(GLFWwindow* w, double x, double y) {...}
... override relevant callbacks ...
};
// in main ************************************************
// Assuming initialized GLFWwindow* my_window and my_other_window
MyResponder resp;
MyResponder resp2; // Can be another subclass of GLFWResponder
// Two responders can respond to same window
resp.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
resp2.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
// One responder can respond to multiple windows
resp2.respond_to(my_other_window, GLFWResponder::CURSOR_POSITION);
// One window can have different handlers for different events
resp.respond_to(my_other_window, GLFWResponder::CURSOR_ENTER);
Proposed Solution Implementation
建议的解决方案实施
Here is the sketch of the GLFWResponder
implementation, fully functional, but with some TODO's. There may be some implications on performance, which I have not yet investigated.
这是实现的草图GLFWResponder
,功能齐全,但有一些 TODO。可能对性能有一些影响,我还没有调查过。
// GLFWResponder.h ************************************************
/**
* Responder superclass that allows subclasses to handle events from multiple
* GLFW windows (which have only C API for callbacks).
* Callbacks are automatically cleaned up when responder goes out of scope.
*/
class GLFWResponder {
public:
virtual ~GLFWResponder();
// Interface -----------------------------------
enum GLFWEventType {
CURSOR_POSITION = 0,
CURSOR_ENTER = 1
// TODO: add support for other callbacks
};
void respond_to(GLFWwindow* window, GLFWEventType event);
bool does_respond_to(GLFWwindow* window, GLFWEventType event) const;
// Subclasses implement ------------------------
virtual void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);
virtual void cursor_enter_callback(GLFWwindow* window, int entered);
// TODO: add support for other callbacks
// Under the hood ------------------------------
static std::set<GLFWResponder*> getResponders(GLFWwindow* windo, GLFWEventType event);
private:
// Windows and events that this instance responds to
std::set<std::pair<GLFWwindow*, GLFWEventType> > enabled_events_;
// Global responders keyed by events they respond to
// (each responder knows which windows it responds to)
static std::map<GLFWEventType, std::set<GLFWResponder*> > responders_;
};
// GLFWResponder.cpp **************************************************
namespace {
void cursor_position_callback_private(GLFWwindow* window, double xpos, double ypos) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_POSITION)) {
r->cursor_position_callback(window, xpos, ypos);
}
}
void cursor_enter_callback_private(GLFWwindow* window, int entered) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_ENTER)) {
r->cursor_enter_callback(window, entered);
}
}
} // namespace
std::map<GLFWResponder::GLFWEventType, std::set<GLFWResponder*> > GLFWResponder::responders_;
GLFWResponder::~GLFWResponder() {
for (auto& pr : responders_) {
pr.second.erase(this);
}
// TODO: also clean up window's callbacks
}
void GLFWResponder::respond_to(GLFWwindow* window, GLFWResponder::GLFWEventType event) {
enabled_events_.insert(std::make_pair(window, event));
responders_[event].insert(this);
if (event == CURSOR_POSITION) {
glfwSetCursorPosCallback(window, cursor_position_callback_private);
} else if (event == CURSOR_ENTER) {
glfwSetCursorEnterCallback(window, cursor_enter_callback_private);
} else {
// TODO: add support for other callbacks
LOG(FATAL) << "Unknown GLFWResponder event: " << event;
}
}
bool GLFWResponder::does_respond_to(GLFWwindow* window, GLFWEventType event) const {
return enabled_events_.find(std::make_pair(window, event)) != enabled_events_.end();
}
std::set<GLFWResponder*> GLFWResponder::getResponders(
GLFWwindow* window, GLFWEventType event) {
std::set<GLFWResponder*> result;
auto it = responders_.find(event);
if (it != responders_.end()) {
for (GLFWResponder* resp : it->second) {
if (resp->does_respond_to(window, event)) {
result.insert(resp);
}
}
}
return result;
}
void GLFWResponder::cursor_position_callback(
GLFWwindow* window, double xpos, double ypos) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}
void GLFWResponder::cursor_enter_callback(GLFWwindow* window, int entered) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}