Pregunta

Estoy usando una API que requiere que pase un puntero de función como devolución de llamada.Estoy intentando utilizar esta API de mi clase pero obtengo errores de compilación.

Esto es lo que hice desde mi constructor:

m_cRedundencyManager->Init(this->RedundencyManagerCallBack);

Esto no se compila; aparece el siguiente error:

Error 8 error C3867:'CLoggersInfra::RedundencyManagerCallBack':llamada a función falta lista de argumentos;use '&CLoggersInfra::RedundencyManagerCallBack' para crear un puntero al miembro

Probé la sugerencia de usar &CLoggersInfra::RedundencyManagerCallBack - no funcionó para mí.

¿Alguna sugerencia/explicación para esto?

Estoy usando VS2008.

¡¡Gracias!!

¿Fue útil?

Solución

Eso no funciona porque un puntero de función miembro no se puede manejar como un puntero de función normal, porque espera un " this " argumento de objeto

En su lugar, puede pasar una función miembro estática de la siguiente manera, que son como funciones normales que no son miembros a este respecto:

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

La función se puede definir de la siguiente manera

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

Otros consejos

Esta es una pregunta simple pero la respuesta es sorprendentemente compleja. La respuesta corta es que puede hacer lo que está tratando de hacer con std :: bind1st o boost :: bind. La respuesta más larga está debajo.

El compilador es correcto al sugerirle que use & amp; CLoggersInfra :: RedundencyManagerCallBack. Primero, si RedundencyManagerCallBack es una función miembro, la función en sí no pertenece a ninguna instancia particular de la clase CLoggersInfra. Pertenece a la clase misma. Si alguna vez llamó a una función de clase estática, es posible que haya notado que usa la misma sintaxis SomeClass :: SomeMemberFunction. Dado que la función en sí es 'estática' en el sentido de que pertenece a la clase en lugar de a una instancia particular, utiliza la misma sintaxis. El amplificador '&; es necesario porque técnicamente no se pasan funciones directamente; las funciones no son objetos reales en C ++. En cambio, técnicamente está pasando la dirección de memoria para la función, es decir, un puntero a donde comienzan las instrucciones de la función en la memoria. Sin embargo, la consecuencia es la misma, efectivamente está 'pasando una función' como parámetro.

Pero eso es solo la mitad del problema en este caso. Como dije, RedundencyManagerCallBack la función no 'pertenece' a ninguna instancia en particular. Pero parece que desea pasarlo como una devolución de llamada con una instancia particular en mente. Para comprender cómo hacer esto, debe comprender qué son realmente las funciones miembro: funciones regulares no definidas en cualquier clase con un parámetro oculto adicional.

Por ejemplo:

class A {
public:
    A() : data(0) {}
    void foo(int addToData) { this->data += addToData; }

    int data;
};

...

A an_a_object;
an_a_object.foo(5);
A::foo(&an_a_object, 5); // This is the same as the line above!
std::cout << an_a_object.data; // Prints 10!

¿Cuántos parámetros toma A :: foo? Normalmente diríamos 1. Pero debajo del capó, foo realmente toma 2. Mirando la definición de A :: foo, necesita una instancia específica de A para que el puntero 'this' sea significativo (el compilador necesita saber qué ' esto es). La forma en que generalmente especifica lo que quiere que sea 'esto' es a través de la sintaxis MyObject.MyMemberFunction (). Pero esto es solo azúcar sintáctico para pasar la dirección de MyObject como primer parámetro a MyMemberFunction. De manera similar, cuando declaramos funciones miembro dentro de las definiciones de clase, no ponemos 'esto' en la lista de parámetros, pero esto es solo un regalo de los diseñadores de idiomas para guardar la escritura. En su lugar, debe especificar que una función miembro es estática para optar por no obtener automáticamente el parámetro adicional 'this'. Si el compilador de C ++ tradujo el ejemplo anterior al código C (el compilador de C ++ original realmente funcionó de esa manera), probablemente escribiría algo como esto:

struct A {
    int data;
};

void a_init(A* to_init)
{
    to_init->data = 0;
}

void a_foo(A* this, int addToData)
{ 
    this->data += addToData;
}

...

A an_a_object;
a_init(0); // Before constructor call was implicit
a_foo(&an_a_object, 5); // Used to be an_a_object.foo(5);

Volviendo a su ejemplo, ahora hay un problema obvio. 'Init' quiere un puntero a una función que tome un parámetro. Pero & Amp; CLoggersInfra :: RedundencyManagerCallBack es un puntero a una función que toma dos parámetros, es el parámetro normal y el parámetro secreto 'this'. Por lo tanto, sigue obteniendo un error del compilador (como nota al margen: si alguna vez ha usado Python, este tipo de confusión es la razón por la cual se requiere un parámetro 'self' para todas las funciones de los miembros).

La forma detallada de manejar esto es crear un objeto especial que contenga un puntero a la instancia que desee y tenga una función miembro llamada algo como 'ejecutar' o 'ejecutar' (o sobrecargar el operador '()') que toma los parámetros para la función miembro y simplemente llama a la función miembro con esos parámetros en la instancia almacenada. Pero esto requeriría que cambie 'Init' para tomar su objeto especial en lugar de un puntero de función sin formato, y parece que Init es el código de otra persona. Y hacer una clase especial para cada vez que surja este problema conducirá a la hinchazón de código.

Entonces, ahora, finalmente, la buena solución, boost :: bind and boost :: function, la documentación para cada uno puede encontrar aquí:

boost :: bind docs , boost :: documentos de función

boost :: bind le permitirá tomar una función y un parámetro para esa función, y crear una nueva función donde ese parámetro está 'bloqueado' en su lugar. Entonces, si tengo una función que agrega dos enteros, puedo usar boost :: bind para crear una nueva función donde uno de los parámetros esté bloqueado para decir 5. Esta nueva función solo tomará un parámetro entero y siempre agregará 5 específicamente lo. Con esta técnica, puede 'bloquear' el parámetro 'this' oculto para que sea una instancia de clase particular y generar una nueva función que solo tome un parámetro, tal como lo desee (tenga en cuenta que el parámetro oculto siempre es primer parámetro , y los parámetros normales se ordenan después). Mire los documentos de boost :: bind para ver ejemplos, incluso discuten específicamente su uso para funciones miembro. Técnicamente, existe una función estándar llamada std :: bind1st que también podría usar, pero boost :: bind es más general.

Por supuesto, solo hay una trampa más. boost :: bind será una buena función boost :: para ti, pero técnicamente aún no es un puntero de función en bruto como probablemente probablemente Init. Afortunadamente, boost proporciona una forma de convertir boost :: function's en punteros sin procesar, como se documenta en StackOverflow aquí . Cómo implementa esto está más allá del alcance de esta respuesta, aunque también es interesante.

No se preocupe si esto parece ridículamente difícil: su pregunta se cruza con varias de las esquinas más oscuras de C ++, y boost :: bind es increíblemente útil una vez que lo aprende.

Actualización de C ++ 11: en lugar de boost :: bind, ahora puede usar una función lambda que captura 'esto'. Esto es básicamente hacer que el compilador genere lo mismo para usted.

Esta respuesta es una respuesta a un comentario anterior y no funciona con VisualStudio 2008, pero debería preferirse con compiladores más recientes.


Mientras tanto, ya no tienes que usar un puntero vacío y tampoco hay necesidad de impulso ya que std::bind y std::function están disponibles. Uno La ventaja (en comparación con los punteros nulos) es la seguridad de tipos, ya que el tipo de retorno y los argumentos se indican explícitamente usando std::function:

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

Luego puedes crear el puntero de función con std::bind y pasarlo a Init:

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

Ejemplo completo Para usar std::bind con funciones miembro, miembros estáticos y no miembros:

#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";
}

Posible salida:

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

Además usando std::placeholders puede pasar argumentos dinámicamente a la devolución de llamada (p. ej.esto permite el uso de return f("MyString"); en Init si f tiene un parámetro de cadena).

¿Qué argumento toma Init? ¿Cuál es el nuevo mensaje de error?

Los punteros de método en C ++ son un poco difíciles de usar. Además del puntero del método en sí, también debe proporcionar un puntero de instancia (en su caso this). ¿Quizás <=> lo espera como un argumento separado?

Un puntero a una función miembro de la clase no es lo mismo que un puntero a una función. Un miembro de la clase toma un argumento adicional implícito (el puntero this ) y usa una convención de llamada diferente.

Si su API espera una función de devolución de llamada no miembro, eso es lo que tiene que pasarle.

¿Es m_cRedundencyManager capaz de usar funciones miembro? La mayoría de las devoluciones de llamada están configuradas para usar funciones regulares o funciones miembro estáticas. Eche un vistazo a esta página en C ++ FAQ Lite para más información.

Actualización: La declaración de función que proporcionó muestra que void yourCallbackFunction(int, void *) espera una función de la forma: void *. Por lo tanto, las funciones de los miembros son inaceptables como devoluciones de llamada en este caso. Una función miembro estática puede funcionar, pero si eso es inaceptable en su caso, el siguiente código también funcionaría. Tenga en cuenta que utiliza un elenco malvado de <=>.


// 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);
}

Puedo ver que el init tiene la siguiente anulación:

Init(CALLBACK_FUNC_EX callback_func, void * callback_parm)

donde CALLBACK_FUNC_EX es

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

Esta pregunta y respuesta de la < a href = "https://isocpp.org/wiki/faq" rel = "nofollow noreferrer"> C ++ FAQ Lite cubre muy bien su pregunta y las consideraciones involucradas en la respuesta. Breve fragmento de la página web que he vinculado:

  

Don & # 8217; t.

     

Porque una función miembro no tiene sentido sin un objeto para invocar   encendido, no puede & # 8217; t hacer esto directamente (si The X Window System fue   reescrito en C ++, probablemente pasaría referencias a objetos alrededor,   no solo punteros a funciones; naturalmente, los objetos encarnarían el   función requerida y probablemente mucho más).

Nigromancia.
Creo que las respuestas hasta la fecha no están claras.

Hagamos un ejemplo:

Supongamos que tiene una matriz de píxeles (matriz de valores ARGB int8_t)

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

Ahora quieres generar un PNG. Para hacerlo, llama a la función toJpeg

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

donde writeByte es una función de devolución de llamada

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

El problema aquí: la salida FILE * tiene que ser una variable global.
Muy malo si estás en un entorno multiproceso (por ejemplo, un servidor http).

Por lo tanto, necesita alguna forma de hacer que la salida sea una variable no global, al tiempo que conserva la firma de devolución de llamada.

La solución inmediata que viene a la mente es un cierre, que podemos emular usando una clase con una función miembro.

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

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

};

Y luego hazlo

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);

Sin embargo, contrario a las expectativas, esto no funciona.

La razón es que las funciones miembro de C ++ se implementan como funciones de extensión C #.

Entonces tienes

class/struct BadIdea
{
    FILE* m_stream;
}

y

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

}

Entonces, cuando desea llamar a writeByte, necesita pasar no solo la dirección de writeByte, sino también la dirección de la instancia de BadIdea.

Entonces, cuando tiene un typedef para el procedimiento writeByte, y se ve así

typedef void (*WRITE_ONE_BYTE)(unsigned char);

Y tiene una firma writeJpeg que se ve así

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

es fundamentalmente imposible pasar una función miembro de dos direcciones a un puntero de función de una dirección (sin modificar writeJpeg), y no hay forma de evitarlo.

Lo mejor que puedes hacer en C ++ es usar una función lambda:

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

Sin embargo, debido a que lambda no está haciendo nada diferente, que pasar una instancia a una clase oculta (como " BadIdea " -class), debe modificar la firma de writeJpeg.

La ventaja de lambda sobre una clase manual, es que solo necesita cambiar un typedef

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

a

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

Y luego puedes dejar todo lo demás intacto.

También podría usar std :: bind

./configure
make
make install

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

Pero esto, detrás de escena, solo crea una función lambda, que también necesita el cambio en typedef.

Entonces, no, no hay forma de pasar una función miembro a un método que requiere un puntero de función estático.

Pero las lambdas son la manera fácil de evitarlo, siempre que tenga control sobre la fuente.
De lo contrario, no tienes suerte.
No hay nada que puedas hacer con C ++.

Nota:
std :: la función requiere #include <functional>

Sin embargo, dado que C ++ también le permite usar C, puede hacerlo con libffcall en C simple, si no le importa vincular una dependencia.

Descargue libffcall de GNU (al menos en ubuntu, no use el paquete proporcionado por la distribución, está roto), descomprima.

#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;
}

main.c:

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

Y si usa CMake, agregue la biblioteca enlazadora después de add_executable

target_link_libraries(BitmapLion ffcall)

o simplemente podría vincular dinámicamente libffcall

<*>

Nota:
Es posible que desee incluir los encabezados y bibliotecas de libffcall, o crear un proyecto cmake con el contenido de libffcall.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top