Я использую 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.
Спасибо!!





Какой аргумент берет 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).
Я изменил ответ @LimitedAtonement. Спасибо за указание на это. Вы совершенно правы, что ответы только по ссылкам - это ответы низкого качества. Но мы не знали этого еще в 2008 году :-P
Это не работает, потому что указатель на функцию-член не может обрабатываться как обычный указатель на функцию, поскольку он ожидает аргумента объекта «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);
}
Хотя это может быть решение / обходной путь ДЛЯ ОП, я не понимаю, как это является ответом на фактический вопрос.
@StefanSteiger ответ (объяснение) находится в последнем абзаце (по сути: «указатель функции-члена не может обрабатываться как указатель на бесплатную функцию»), а предложение, что делать еще, содержится в других частях моего ответа. Это правда, что это могло быть более подробно. Но это нормально, и именно поэтому мой ответ не получил столько голосов, сколько другие. Иногда более сжатые ответы, которые по сути содержат только код, нуждаются в помощи лучше, чем более длинные, и поэтому мой ответ был принят.
Шауб: Да, именно моя точка зрения. Но я вижу - вы должны были сначала написать последнюю часть, а потом сказать: вместо этого вы можете сделать это + (первая часть)
Это не сработает, если вы хотите, чтобы функция-член вызывалась из другого класса (по крайней мере, я не могу понять, как это сделать ...).
Этот ответ является ответом на комментарий выше и не работает с 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 минуту.
Некромантинг.
Я думаю, что на сегодняшний день ответы немного неясны.
Приведем пример:
Предположим, у вас есть массив пикселей (массив значений 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.
Ссылка теперь isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr; похоже, теперь он говорит «Не надо». Вот почему ответы только по ссылкам не годятся.