Аргумент типа `<метод класса C++>` несовместим с параметром типа `GLFWwindowsizefun` при использовании GLFW внутри класса

В настоящее время я реорганизую свой код в класс (VkAPP) и наткнулся на эту ошибку:

аргумент типа "void (VkAPP::)(GLFWwindow window, int width, int height)" несовместим с параметром типа "GLFWwindowsizefun"

Вот рассматриваемая функция:

void onWindowResized(GLFWwindow* window, int width, int height) {
    VkSurfaceCapabilitiesKHR surfaceCapabilities;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevices[0], surface, &surfaceCapabilities);

    if (width > surfaceCapabilities.maxImageExtent.width) width = surfaceCapabilities.maxImageExtent.width;
    if (height > surfaceCapabilities.maxImageExtent.height) height = surfaceCapabilities.maxImageExtent.height;

    if (width == 0 || height == 0) return;

    WIDTH = width;
    HEIGHT = height;

    recreateSwapchain();
}

Вот звонок:

glfwSetWindowSizeCallback(window, onWindowResized);

Функция и вызов находятся в одном классе.

Если я превращу это в:

static void onWindowResized

Я получил:

нестатическая ссылка на член должна быть относительно определенного объекта

для physicalDevices[0], surface, WIDTH, HEIGHT и recreateSwapchain()

GLFW реализован на C. Он понятия не имеет о таких вещах, как классы и объекты. Указатели на функции, которые он использует, соответствуют функциям, не являющимся членами, в C++. Функция-член нуждается в объекте для вызова, и поэтому указатель на функцию-член — это не то же самое, что указатель на функцию, не являющуюся членом.

Some programmer dude 03.04.2024 18:16

В качестве обходного пути, если вы можете установить какой-то «пользовательские данные» или «указатель пользовательских данных», вы можете использовать функцию-член static. Установите указатель пользовательских данных на указатель на объект и используйте статическую функцию-член в качестве обратного вызова. Это работает, потому что статическим функциям-членам не нужен объект. Затем статическая функция-член извлекает указатель пользовательских данных, использует reinterpret_cast, чтобы привести его к правильному типу объекта, и вызывает фактическую функцию-член.

Some programmer dude 03.04.2024 18:19

@StarFox Делай то, что сказал Someprogrammerdude. Вы можете использовать glfwSetWindowUserPointer и glfwGetWindowUserPointer для передачи «пользовательских данных» в статический обратный вызов.

Remy Lebeau 03.04.2024 20:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
113
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Прототип функции-члена

void onWindowResized(GLFWwindow* window, int width, int height);

на самом деле выглядит примерно так

void onWindowResized(
    MyClass *this, //the famously infamous 'this' pointer
    GLFWwindow* window, 
    int width, 
    int height
);

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

Чтобы связать окно с конкретными данными, GLFW предоставляет механизм, с помощью которого это можно сделать:

void glfwSetWindowUserPointer(GLFWwindow *window, void *pointer);

и получить его через

void* glfwGetWindowUserPointer(GLFWwindow *window);

Чтобы все заработало, вам придется внести некоторые изменения:

//either global or inside class
static void onWindowResized(GLFWwindow* window, int width, int height)
{
    //get your class instance
    MyClass *obj = reinterpret_cast<MyClass*>(glfwGetWindowUserPointer(window));    

    //use the memebers as usual
    //obj->physicalDevices[0];
    //obj->surface;
    //obj->WIDTH;
    //obj->HEIGHT;
    //obj->recreateSwapchain();

    //note: 
    //if function is in global scope, the members have to be public
    //if inside the class, private members can be accessed
}

не забудьте связать экземпляр вашего класса с окном:

//somewhere in your code (but before the callback gets invoked)
glfwSetWindowUserPointer(window, /* MyClass* */ obj);

Примечание. Внимательно следите за сроком жизни экземпляра класса obj.

Ответ принят как подходящий

Начнем с первого сообщения об ошибке, которое вы получаете.

GLFWwindowsizefun определяется следующим образом:

typedef void(* GLFWwindowsizefun) (GLFWwindow *window, int width, int height)

С другой стороны, (нестатический) метод onWindowResized вашего класса C++ имеет неявный указатель на экземпляр класса.

Другими словами, нестатический VkAPP метод вашего onWindowResized класса C++ объявлен следующим образом:

// Inside VkAPP class
void onWindowResized(GLFWwindow* window, int width, int height)

имеет неявный параметр, который является указателем на экземпляр класса VkAPP (т. е. классический указатель this в C++):

void VkAPP::onWindowResized(VkAPP*      this, /* implicit VkAPP* 'this' pointer */ ,
                            GLFWwindow* window, 
                            int         width, 
                            int         height)

Итак, компилятор C++ сообщил вам о несоответствии между ожидаемым GLFWwindowsizefun (у которого нет неявного указателя this) и вашим методом VkAPP::onWindowResized (у которого есть неявный указатель this).

Как можно устранить это несоответствие?

Что ж, вы можете определить статический метод в своем классе VkAPP C++, например:

// Inside VkAPP class
// 
// Note: the method is *static*
static void onWindowResizedStatic(GLFWwindow* window, int width, int height)

Обратите внимание, что методы static не имеют неявного указателя this, поэтому приведенный выше метод соответствует GLFWwindowsizefuntypedef.

Таким образом, вы можете передать его вызову функции glfwSetWindowSizeCallback:

// You had this line, which didn't work as onWindowResized was not static:
//
//     glfwSetWindowSizeCallback(window, onWindowResized);  <<-- error
//
// Pass a *static* method as callback, instead:
//
glfwSetWindowSizeCallback(window, onWindowResizedStatic);

К сожалению, как было показано в другом сообщении об ошибке, вы не можете получить доступ к элементам данных, специфичным для экземпляра, таким как physicalDevices[0], surface, WIDTH, HEIGHT и т. д., из статического метода вашего класса.

Итак, идея здесь состоит в том, чтобы использовать метод static как место для перенаправления вызова на метод вашего класса, специфичный для экземпляра, например:

// Inside VkAPP class
static void onWindowResizedStatic(GLFWwindow* window, int width, int height) {
    // Somehow get the instance-specific "this" pointer
    VkAPP* pThis = ... /* we'll see that later */;

    // Forward the call to the non-static method callback:
    pThis->onWindowResized(window, width, height);
}

Теперь, как получить указатель this из метода static выше?

Что ж, во время инициализации (например, в конструкторе вашего класса C++ или в другом методе инициализации), как только у вас есть действительный объект GLFWwindow (например, созданный с помощью glfwCreateWindow), вы можете вызвать функцию glfwSetWindowUserPointer, чтобы установить специфичный для экземпляра this указатель как общий «пользовательский указатель», связанный с объектом окна:

// Inside a method of your VkAPP C++ class, during initialization:
 
// Set the "this" pointer as window's user pointer for later retrieval
glfwSetWindowUserPointer(window, this);

Затем указатель this можно позже получить, вызвав симметричный glfwGetWindowUserPointer:

// Inside VkAPP class
static void onWindowResizedStatic(GLFWwindow* window, int width, int height) {
    // Get the instance-specific "this" pointer 
    // that was set before during initialization
    VkAPP* pThis = static_cast<VkAPP*>(glfwGetWindowUserPointer(window));

    // Forward the call to the non-static method callback:
    pThis->onWindowResized(window, width, height);
}

Обратите внимание: поскольку ваш метод onWindowResized не является статическим, вы можете использовать все элементы данных, специфичные для экземпляра, такие как physicalDevices[0], surface и т. д., или вызывать другие функции-члены, такие как recreateSwapchain(), на которые жаловались во втором сообщении об ошибке.

Это отлично работает! но если я поставлю его перед созданием окна, оно не будет работать и вернет значение Null в pThis.

StarFox 06.04.2024 21:58

@StarFox: Конечно, у вас уже должен быть действительный объект GLFWwindow, чтобы вызвать glfwSetWindowUserPointer и установить пользовательский указатель пользовательских данных. Я добавил разъяснение по этому поводу в своем ответе.

Mr.C64 07.04.2024 20:07

Для тех, кто ищет быстрый awnser:

/* Resizes the Window when Size changes */
static void windowResizeStatic(GLFWwindow* window, int width, int height) {
  Engine* pThis = static_cast<Engine*>(glfwGetWindowUserPointer(window));
  pThis->onWindowResized(width, height);
}

void onWindowResized(int width, int height) {
  /* rest of the function cut out */
}

void startGLFW() {
  glfwInit();
  glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
  glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

  window = glfwCreateWindow(WIDTH, HEIGHT, "Window Name", nullptr, nullptr);
 //set the glfwSetWindowUserPointer here or it returns a nullptr
  glfwSetWindowUserPointer(window, this);
  glfwSetWindowSizeCallback(window, windowResizeStatic);

  glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
}

Другие вопросы по теме