C++ Zero Overhead Principle – Practical Applications

cobject-oriented-designwrapper

As an exercise in code architecture, I was writing a C++ wrapper for a window library called GLFW3.

In this library, when a window's X button is pressed, you can register a callback that reacts to such event. The callback needs to look like this:

void callback_name(GLFWwindow* handle);

That is, it needs to be a pointer to function that returns nothing and takes a pointer to the window handle.

Now, in my wrapper a window is a proper class called Window. And I would like to transform the GLFW-approach of using callbacks to using an handling function Window::onClose akin to what Qt does. For example, the default onClose function would look like this:

class Window {
    ...
    void onClose() {
        hide(); // remove the window from view and from taskbar
    }
};

The problem

The problem arises when I try to make this work without introducing overhead into the wrapper. The naïve solution is to register the callback in the constructor:

// DOES NOT COMPILE
class Window {
    ...
    Window(...) {
        glfwSetWindowCloseCallback(mHandle, 
            [this](GLFWwindow*) { // When X button is pressed, call this onClose function
                onClose();
        });
    }

This does not compile, because the "callback" needs to capture this pointer to call a member function, and glfwSetWindowCloseCallback expects a raw function pointer.

Getting into more complex solutions, one could maintain a map such as std::unordered_map<GLFWwindow*, Window*> handle_to_window, and do the following:

glfwSetWindowCloseCallback(mHandle, 
    [](GLFWwindow* handle) {
        handle_to_window[handle]->onClose();
    });

BUT this would break with inherited classes unless I make the onClose function virtual (which would introduce overhead).

Question

My question is, is there a way to make this work without introducing more overhead? Answers that change the architecture of the wrapper are fair game also.

Best Answer

It's not quite zero-overhead, but you can use the GLFWwindow User Pointer Property to make a relatively clean solution, I think (caveat: my C++ is a little rusty, and I am not familiar with the GLFW library).

In your Window constructor,

Window(...) {
        // ...
        glfwSetWindowUserPointer(mHandle, static_cast<void *>(this));
        glfwSetWindowCloseCallback(mHandle, Window::onCloseWrapper);
};

And then define onCloseWrapper (in the Window class) as:

static void onCloseWrapper(GLFWwindow *wHandle) {
        Window *w = static_cast<Window*>(glfwGetWindowUserPointer(wHandle));
        w->onClose()
}
Related Topic