Как передать функцию-член класса в качестве обратного вызова?

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

Вот что я сделал из своего конструктора:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Это не компилируется - появляется следующая ошибка:

Error 8 error C3867: 'CLoggersInfra::RedundencyManagerCallBack': function call missing argument list; use '&CLoggersInfra::RedundencyManagerCallBack' to create a pointer to member

Я попробовал предложение использовать &CLoggersInfra::RedundencyManagerCallBack - у меня не сработало.

Любые предложения / объяснения по этому поводу ??

Я использую VS2008.

Спасибо!!

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
82
0
126 640
11
Перейти к ответу Данный вопрос помечен как решенный

Ответы 11

Какой аргумент берет Init? Что за новое сообщение об ошибке?

Указатели методов в C++ немного сложны в использовании. Помимо самого указателя метода, вам также необходимо предоставить указатель экземпляра (в вашем случае this). Может, Init ожидает этого как отдельного аргумента?

Я вижу, что у init есть следующее переопределение:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

где CALLBACK_FUNC_EX - это

typedef void (*CALLBACK_FUNC_EX)(int, void *);

Может ли m_cRedundencyManager использовать функции-члены? Большинство обратных вызовов настроены на использование обычных функций или статических функций-членов. Взгляните на эта страница в C++ FAQ Lite для получения дополнительной информации.

Обновлять: Предоставленное вами объявление функции показывает, что m_cRedundencyManager ожидает функцию вида: void yourCallbackFunction(int, void *). Следовательно, в этом случае функции-члены неприемлемы в качестве обратных вызовов. Статическая функция-член май работает, но если это неприемлемо в вашем случае, следующий код также будет работать. Обратите внимание, что он использует злой состав из void *.


// in your CLoggersInfra constructor:
m_cRedundencyManager->Init(myRedundencyManagerCallBackHandler, this);

// in your CLoggersInfra header:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr);

// in your CLoggersInfra source file:
void myRedundencyManagerCallBackHandler(int i, void * CLoggersInfraPtr)
{
    ((CLoggersInfra *)CLoggersInfraPtr)->RedundencyManagerCallBack(i);
}

Указатель на функцию-член класса - это не то же самое, что указатель на функцию. Член класса принимает неявный дополнительный аргумент (указатель это) и использует другое соглашение о вызовах.

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

Этот вопрос и ответ из C++ FAQ Lite охватывает ваш вопрос и соображения, связанные с ответом, я думаю, довольно хорошо. Короткий фрагмент с веб-страницы, на которую я указал:

Don’t.

Because a member function is meaningless without an object to invoke it on, you can’t do this directly (if The X Window System was rewritten in C++, it would probably pass references to objects around, not just pointers to functions; naturally the objects would embody the required function and probably a whole lot more).

Ссылка теперь isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr; похоже, теперь он говорит «Не надо». Вот почему ответы только по ссылкам не годятся.

lmat - Reinstate Monica 12.01.2016 22:45

Я изменил ответ @LimitedAtonement. Спасибо за указание на это. Вы совершенно правы, что ответы только по ссылкам - это ответы низкого качества. Но мы не знали этого еще в 2008 году :-P

Onorio Catenacci 13.01.2016 19:04
Ответ принят как подходящий

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

Вместо этого вы можете передать статическую функцию-член следующим образом, которые в этом отношении похожи на обычные функции, не являющиеся членами:

m_cRedundencyManager->Init(&CLoggersInfra::Callback, this);

Функцию можно определить следующим образом

static void Callback(int other_arg, void * this_pointer) {
    CLoggersInfra * self = static_cast<CLoggersInfra*>(this_pointer);
    self->RedundencyManagerCallBack(other_arg);
}

Хотя это может быть решение / обходной путь ДЛЯ ОП, я не понимаю, как это является ответом на фактический вопрос.

Stefan Steiger 27.03.2019 13:29

@StefanSteiger ответ (объяснение) находится в последнем абзаце (по сути: «указатель функции-члена не может обрабатываться как указатель на бесплатную функцию»), а предложение, что делать еще, содержится в других частях моего ответа. Это правда, что это могло быть более подробно. Но это нормально, и именно поэтому мой ответ не получил столько голосов, сколько другие. Иногда более сжатые ответы, которые по сути содержат только код, нуждаются в помощи лучше, чем более длинные, и поэтому мой ответ был принят.

Johannes Schaub - litb 27.03.2019 13:37

Шауб: Да, именно моя точка зрения. Но я вижу - вы должны были сначала написать последнюю часть, а потом сказать: вместо этого вы можете сделать это + (первая часть)

Stefan Steiger 27.03.2019 19:54

Это не сработает, если вы хотите, чтобы функция-член вызывалась из другого класса (по крайней мере, я не могу понять, как это сделать ...).

dmedine 05.02.2021 04:13

Этот ответ является ответом на комментарий выше и не работает с VisualStudio 2008, но его следует предпочесть более поздним компиляторам.


Между тем вам больше не нужно использовать указатель void, и нет необходимости в повышении, поскольку доступны std::bind и std::function. Преимущество Один (по сравнению с указателями void) заключается в безопасности типов, поскольку возвращаемый тип и аргументы явно указаны с использованием std::function:

// std::function<return_type(list of argument_type(s))>
void Init(std::function<void(void)> f);

Затем вы можете создать указатель на функцию с помощью std::bind и передать его в Init:

auto cLoggersInfraInstance = CLoggersInfra();
auto callback = std::bind(&CLoggersInfra::RedundencyManagerCallBack, cLoggersInfraInstance);
Init(callback);

Полный пример для использования std::bind с членами, статическими членами и функциями, не являющимися членами:

#include <functional>
#include <iostream>
#include <string>

class RedundencyManager // incl. Typo ;-)
{
public:
    // std::function<return_type(list of argument_type(s))>
    std::string Init(std::function<std::string(void)> f) 
    {
        return f();
    }
};

class CLoggersInfra
{
private:
    std::string member = "Hello from non static member callback!";

public:
    static std::string RedundencyManagerCallBack()
    {
        return "Hello from static member callback!";
    }

    std::string NonStaticRedundencyManagerCallBack()
    {
        return member;
    }
};

std::string NonMemberCallBack()
{
    return "Hello from non member function!";
}

int main()
{
    auto instance = RedundencyManager();

    auto callback1 = std::bind(&NonMemberCallBack);
    std::cout << instance.Init(callback1) << "\n";

    // Similar to non member function.
    auto callback2 = std::bind(&CLoggersInfra::RedundencyManagerCallBack);
    std::cout << instance.Init(callback2) << "\n";

    // Class instance is passed to std::bind as second argument.
    // (heed that I call the constructor of CLoggersInfra)
    auto callback3 = std::bind(&CLoggersInfra::NonStaticRedundencyManagerCallBack,
                               CLoggersInfra()); 
    std::cout << instance.Init(callback3) << "\n";
}

Возможный выход:

Hello from non member function!
Hello from static member callback!
Hello from non static member callback!

Кроме того, используя std::placeholders, вы можете динамически передавать аргументы обратному вызову (например, это позволяет использовать return f("MyString"); в Init, если f имеет строковый параметр).

Настоящее, очень большое спасибо от меня за этот ответ! Я потратил уже более двух часов на поиск и опробование различных подходов, но ничего не помогло. Но этот настолько простой, что он сработал уже через 1 минуту.

BadK 02.10.2018 15:11

Некромантинг.
Я думаю, что на сегодняшний день ответы немного неясны.

Приведем пример:

Предположим, у вас есть массив пикселей (массив значений ARGB int8_t)

// A RGB image
int8_t* pixels = new int8_t[1024*768*4];

Теперь вы хотите сгенерировать PNG. Для этого вы вызываете функцию toJpeg

bool ok = toJpeg(writeByte, pixels, width, height);

где writeByte - это callback-функция

void writeByte(unsigned char oneByte)
{
    fputc(oneByte, output);
}

Проблема здесь: вывод FILE * должен быть глобальной переменной.
Очень плохо, если вы работаете в многопоточной среде (например, на http-сервере).

Итак, вам нужен способ сделать вывод неглобальной переменной, сохранив при этом подпись обратного вызова.

Непосредственным решением, которое приходит в голову, является закрытие, которое мы можем эмулировать, используя класс с функцией-членом.

class BadIdea {
private:
    FILE* m_stream;
public:
    BadIdea(FILE* stream)  {
        this->m_stream = stream;
    }

    void writeByte(unsigned char oneByte){
            fputc(oneByte, this->m_stream);
    }

};

А потом сделай

FILE *fp = fopen(filename, "wb");
BadIdea* foobar = new BadIdea(fp);

bool ok = TooJpeg::writeJpeg(foobar->writeByte, image, width, height);
delete foobar;
fflush(fp);
fclose(fp);

Однако вопреки ожиданиям это не работает.

Причина в том, что функции-члены C++ реализованы как функции расширения C#.

Так что у тебя есть

class/struct BadIdea
{
    FILE* m_stream;
}

а также

static class BadIdeaExtensions
{
    public static writeByte(this BadIdea instance, unsigned char oneByte)
    {
         fputc(oneByte, instance->m_stream);
    }

}

Поэтому, когда вы хотите вызвать writeByte, вам нужно передать не только адрес writeByte, но и адрес экземпляра BadIdea.

Итак, когда у вас есть typedef для процедуры writeByte, и он выглядит так

typedef void (*WRITE_ONE_BYTE)(unsigned char);

И у вас есть подпись writeJpeg, которая выглядит так

bool writeJpeg(WRITE_ONE_BYTE output, uint8_t* pixels, uint32_t 
 width, uint32_t height))
    { ... }

принципиально невозможно передать двухадресную функцию-член в одноадресный указатель функции (без изменения writeJpeg), и нет никакого способа обойти это.

Следующее лучшее, что вы можете сделать в C++, - это использовать лямбда-функцию:

FILE *fp = fopen(filename, "wb");
auto lambda = [fp](unsigned char oneByte) { fputc(oneByte, fp);  };
bool ok = TooJpeg::writeJpeg(lambda, image, width, height);

Однако, поскольку лямбда не делает ничего другого, кроме передачи экземпляра скрытому классу (например, классу «BadIdea»), вам необходимо изменить подпись writeJpeg.

Преимущество лямбда над ручным классом заключается в том, что вам просто нужно изменить один typedef

typedef void (*WRITE_ONE_BYTE)(unsigned char);

к

using WRITE_ONE_BYTE = std::function<void(unsigned char)>; 

И тогда вы можете оставить все остальное нетронутым.

Вы также можете использовать std :: bind

auto f = std::bind(&BadIdea::writeByte, &foobar);

Но это за кулисами просто создает лямбда-функцию, которая затем также требует изменения typedef.

Итак, нет, нет способа передать функцию-член методу, который требует статического указателя на функцию.

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

Примечание:
std :: function требует #include <functional>

Однако, поскольку C++ позволяет вам также использовать C, вы можете сделать это с помощью libffcall на простом C, если вы не против связывания зависимости.

Загрузите libffcall из GNU (по крайней мере, на ubuntu, не используйте предоставленный дистрибутивом пакет - он сломан), разархивируйте.

./configure
make
make install

gcc main.c -l:libffcall.a -o ma

main.c:

#include <callback.h>

// this is the closure function to be allocated 
void function (void* data, va_alist alist)
{
     int abc = va_arg_int(alist);

     printf("data: %08p\n", data); // hex 0x14 = 20
     printf("abc: %d\n", abc);

     // va_start_type(alist[, return_type]);
     // arg = va_arg_type(alist[, arg_type]);
     // va_return_type(alist[[, return_type], return_value]);

    // va_start_int(alist);
    // int r = 666;
    // va_return_int(alist, r);
}



int main(int argc, char* argv[])
{
    int in1 = 10;

    void * data = (void*) 20;
    void(*incrementer1)(int abc) = (void(*)()) alloc_callback(&function, data);
    // void(*incrementer1)() can have unlimited arguments, e.g. incrementer1(123,456);
    // void(*incrementer1)(int abc) starts to throw errors...
    incrementer1(123);
    // free_callback(callback);
    return EXIT_SUCCESS;
}

И если вы используете CMake, добавьте библиотеку компоновщика после add_executable

add_library(libffcall STATIC IMPORTED)
set_target_properties(libffcall PROPERTIES
        IMPORTED_LOCATION /usr/local/lib/libffcall.a)
target_link_libraries(BitmapLion libffcall)

или вы можете просто динамически связать libffcall

target_link_libraries(BitmapLion ffcall)

Примечание:
Вы можете включить заголовки и библиотеки libffcall или создать проект cmake с содержимым libffcall.

Простое «обходное решение» по-прежнему состоит в том, чтобы создать класс виртуальных функций «interface» и унаследовать его от класса вызывающей стороны. Затем передайте его как параметр «может быть в конструкторе» другого класса, который вы хотите вызвать обратно в свой класс вызывающего.

ОПРЕДЕЛЕНИЕ Интерфейса:

class CallBack 
{
   virtual callMeBack () {};
};

Это класс, которому вы хотите перезвонить:

class AnotherClass ()
{
     public void RegisterMe(CallBack *callback)
     {
         m_callback = callback;
     }

     public void DoSomething ()
     {
        // DO STUFF
        // .....
        // then call
        if (m_callback) m_callback->callMeBack();
     }
     private CallBack *m_callback = NULL;
};

И этот класс будет отозван.

class Caller : public CallBack
{
    void DoSomthing ()
    {
    }

    void callMeBack()
    {
       std::cout << "I got your message" << std::endl;
    }
};

Тип указатель на нестатическую функцию-член отличается от указатель на обычную функцию.
Тип - void(*)(int), если это функция-член обычный или статический. Тип - void(CLoggersInfra::*)(int), если это функция-член нестатический. Таким образом, вы не можете передать указатель на нестатическую функцию-член, если она ожидает обычный указатель функции.

Кроме того, нестатическая функция-член имеет неявный / скрытый параметр для объекта. Указатель this неявно передается в качестве аргумента при вызове функции-члена. Таким образом, функции-члены могут быть вызваны Только, предоставив объект.

Если изменить API Initне могу, можно использовать функцию-оболочку (обычная функция или статическая функция-член класса), которая вызывает этот член. В худшем случае объект будет глобальным для доступа функции оболочки.

CLoggersInfra* pLoggerInfra;

RedundencyManagerCallBackWrapper(int val)
{
    pLoggerInfra->RedundencyManagerCallBack(val);
}
m_cRedundencyManager->Init(RedundencyManagerCallBackWrapper);

Если изменить API Initможет, есть много альтернатив - указатель на нестатическую функцию-член объекта, объект функции, std::function или интерфейсную функцию.

См. Сообщение на обратные вызовы для различных вариантов с Примеры работы на C++.

Похоже, std::mem_fn (C++ 11) делает именно то, что вам нужно:

Function template std::mem_fn generates wrapper objects for pointers to members, which can store, copy, and invoke a pointer to member. Both references and pointers (including smart pointers) to an object can be used when invoking a std::mem_fn.

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