Тип вызываемого объекта не является функцией или указателем с Typedef и классами

Я пытаюсь передать функцию моего класса void write_log(String verbosity, Variant message); другому классу, который будет обрабатывать цикл обработки событий (ev.h). String и Variant взяты из пространства имен godot.

Я использовал typedef, чтобы преобразовать write_log в тип Logger, используя определение typedef void (godot::MQTT::*Logger)(godot::String, godot::Variant);. Это определение хранится в вызываемом классе, а не в вызывающем классе.

Вызываемая функция определяется как void initialize(Logger logger);, которая затем вызывается с помощью приведенного ниже кода.

libumqtt::Client client;
client.initialize(&MQTT::write_log);

Мой вызывающий класс — godot::MQTT, а вызываемый класс — libumqtt::Client.

Моя ошибка возникает, когда я пытаюсь использовать write_log.

void Client::initialize(Logger write_log) {
  ...

  write_log("error", "Initializing MQTT Client!!! TypeDEF!!!");

  ...
}

Сообщение об ошибке, как показано ниже.

➜  Godot-MQTT-Module git:(master) ✗ scons platform=osx bits=64
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
g++ -o src/MQTT.os -c -g -O2 -arch x86_64 -std=c++17 -fPIC -I. -Igodot-cpp/godot_headers -Igodot-cpp/include -Igodot-cpp/include/core -Igodot-cpp/include/gen -Isrc -Ilibraries/libumqtt/src src/MQTT.cpp
g++ -o src/client.os -c -g -O2 -arch x86_64 -std=c++17 -fPIC -I. -Igodot-cpp/godot_headers -Igodot-cpp/include -Igodot-cpp/include/core -Igodot-cpp/include/gen -Isrc -Ilibraries/libumqtt/src src/client.cpp
src/client.cpp:120:14: error: called object type 'libumqtt::Client::Logger' (aka 'void (godot::MQTT::*)(godot::String, godot::Variant)') is not a function or function pointer
    write_log("error", "Initializing MQTT Client!!! TypeDEF!!!");
    ~~~~~~~~~^
1 error generated.
scons: *** [src/client.os] Error 1
scons: building terminated because of errors.

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

Я также использовал эти ссылки, чтобы научиться использовать typedef и передавать указатель на функцию:

Изменить: при неинициализированном упоминании моего кода, выглядящего правильно, но количество опубликованных сообщений ограничено, я вставляю ссылки на классы и файлы заголовков из моего репо. Эти ссылки отмечены для этой конкретной фиксации, поэтому содержимое не изменится, даже когда я обновлю код. Кроме того, эта библиотека принадлежит Массачусетскому технологическому институту, поэтому вам не придется беспокоиться об авторских правах.

Редактировать 2: Используя этот ответ, я проверил тип переменной, которую я пытаюсь использовать write_log в вызываемом классе, и это возвращает то, что я ожидал. Итак, я понятия не имею, почему компилятор не может видеть, что это функция. Я использую С++ 17 для компиляции своего кода, а С++ 14 имеет ту же проблему (С++ 11 прерывает другой код, даже не дойдя до этого, поэтому он не дает полезных результатов).

void (godot::MQTT::*)(godot::String, godot::Variant)


person Alexis Evelyn    schedule 08.12.2019    source источник
comment
Не знаю, поможет ли это, но исходная функция write_log помечена как приватная в заголовочном файле.   -  person Alexis Evelyn    schedule 08.12.2019
comment
Я пометил функцию приватной, так как класс регистратора, который вызывается из write_log, передается из игрового движка. Класс регистратора написан с использованием GDScript. Оригинальный write_log, который я пытаюсь передать своему второму классу, отлично работает, просто он не будет работать статически.   -  person Alexis Evelyn    schedule 08.12.2019
comment
Мне удалось заставить его работать. Это как-то связано с передачей ссылки на сам класс. Я использовал std::invoke для вызова функции. Я опубликую то, что я сделал позже, но для тех, кто хочет просмотреть это сейчас, посмотрите на это ссылка на Github.   -  person Alexis Evelyn    schedule 09.12.2019


Ответы (2)


Из ограниченного фрагмента кода, который вы опубликовали, похоже, нет никакой ошибки. Однако известно, что компилятор gcc/g++ выдает эту ошибку, если в строках, предшествующих этому вызову функции, есть проблемы с синтаксисом, такие как точка с запятой и т. д.

person Uninitialized    schedule 08.12.2019
comment
Я проверил линии раньше, и они не сломаны. Это просто переменные, помогающие настроить использование libev. - person Alexis Evelyn; 08.12.2019

Чтобы ответить на мой собственный вопрос, я буду повторять и объяснять код, который я написал.

У нас есть два класса, которые хранятся в двух файлах: MQTT.cpp и Client.cpp. MQTT.cpp имеет пространство имен «godot» и имя класса «MQTT» (то есть godot::MQTT::). Client.cpp имеет пространство имен «libumqtt» и имя класса «Client» (то есть libumqtt::Client::).

MQTT имеет функцию, которая определена как void write_log(String verbosity, Variant message);. Она также имеет функцию initialize();. Аргументы и тип возвращаемого значения не имеют значения для инициализации. Чтобы инициализировать клиента, я создаю объект Client, а затем вызываю его собственный метод, который также называется Initialize.

// MQTT.cpp
String MQTT::initialize() {
    // ...

    // This code will initialize the Client class and pass the function `write_log` to the Client object.
    // `write_log` is marked as private in the header file MQTT.hpp.
    libumqtt::Client client;
    client.initialize(*this, &MQTT::write_log); // Initialize Instance of Client

    // ...
}

В Client.hpp я пометил инициализацию как общедоступную и добавил строку void initialize(godot::MQTT& mqtt_class, Logger logger);. Я также добавил typedef typedef void (godot::MQTT::*Logger)(godot::String, godot::Variant);, чтобы я мог просто ссылаться на тип данных write_log как на Logger.

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

// Client.cpp
void Client::initialize(godot::MQTT& mqtt_class, Logger write_log) {
    // ...

    // Send Test Log
    // Why Invoke? - https://isocpp.org/wiki/faq/pointers-to-members#macro-for-ptr-to-memfn
    std::invoke(write_log, mqtt_class, "error", "Initializing MQTT Client!!! TypeDEF!!!");

    // ...
}

Что я делал неправильно, так это то, что я пытался вызвать указатель на функцию-член без использования вызова. Поскольку я новичок в cpp, я бы не знал о том, что я не могу просто вызвать указатель напрямую, если он переходит к другой функции, которая не является непосредственной частью моего класса. Использовать Invoke проще, чем пытаться использовать ((object).*(ptrToMember)), синтаксис которого я еще не до конца понимаю.

Что я сделал, так это создал указатель на свой класс godot::MQTT, захватив указатель ключевого слова this. Затем я создаю ссылку на указатель, предоставленный this, через переменную godot::MQTT& mqtt_class. После этого вызвать функцию write_log так же просто, как предоставить ссылку на объект вызывающего класса, а затем указатель на функцию в вызывающем классе, а также аргументы, необходимые для вызова функции.

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

person Alexis Evelyn    schedule 09.12.2019