¿Cómo puedo pasar una función miembro donde se espera una función libre?

Resuelto Jorge Leitao asked hace 12 años • 9 respuestas

La pregunta es la siguiente: considere este fragmento de código:

#include <iostream>


class aClass
{
public:
    void aTest(int a, int b)
    {
        printf("%d + %d = %d", a, b, a + b);
    }
};

void function1(void (*function)(int, int))
{
    function(1, 1);
}

void test(int a,int b)
{
    printf("%d - %d = %d", a , b , a - b);
}

int main()
{
    aClass a;

    function1(&test);
    function1(&aClass::aTest); // <-- How should I point to a's aClass::test function?
}

¿ Cómo puedo usar a's aClass::testcomo argumento function1? Me gustaría acceder a un miembro de la clase.

Jorge Leitao avatar Sep 30 '12 23:09 Jorge Leitao
Aceptado

No hay nada de malo en usar punteros de función. Sin embargo, los punteros a funciones miembro no estáticas no son como punteros a funciones normales: las funciones miembro deben llamarse en un objeto que se pasa como argumento implícito a la función. La firma de su función miembro anterior es, por lo tanto

void (aClass::*)(int, int)

en lugar del tipo que intentas usar

void (*)(int, int)

Un enfoque podría consistir en hacer que el miembro funcione, staticen cuyo caso no requiere que se invoque ningún objeto y puede usarlo con el tipo void (*)(int, int).

Si necesita acceder a cualquier miembro no estático de su clase y necesita seguir con los punteros de función, por ejemplo, porque la función es parte de una interfaz C, su mejor opción es siempre pasar a void*su función tomando punteros de función y llamando su miembro a través de una función de reenvío que obtiene un objeto de void*y luego llama a la función miembro.

En una interfaz C++ adecuada, es posible que desee echar un vistazo a que su función tome argumentos con plantilla para que los objetos de función utilicen tipos de clases arbitrarios. Si no desea utilizar una interfaz con plantilla, debería utilizar algo como std::function<void(int, int)>: puede crear un objeto de función invocable adecuado para estos, por ejemplo, utilizando std::bind().

Los enfoques de seguridad de tipos que utilizan un argumento de plantilla para el tipo de clase o un adecuado std::function<...>son preferibles que usar una void*interfaz, ya que eliminan la posibilidad de errores debido a una conversión al tipo incorrecto.

Para aclarar cómo utilizar un puntero de función para llamar a una función miembro, aquí hay un ejemplo:

// the function using the function pointers:
void somefunction(void (*fptr)(void*, int, int), void* context) {
    fptr(context, 17, 42);
}

void non_member(void*, int i0, int i1) {
    std::cout << "I don't need any context! i0=" << i0 << " i1=" << i1 << "\n";
}

struct foo {
    void member(int i0, int i1) {
        std::cout << "member function: this=" << this << " i0=" << i0 << " i1=" << i1 << "\n";
    }
};

void forwarder(void* context, int i0, int i1) {
    static_cast<foo*>(context)->member(i0, i1);
}

int main() {
    somefunction(&non_member, nullptr);
    foo object;
    somefunction(&forwarder, &object);
}
Dietmar Kühl avatar Sep 30 '2012 16:09 Dietmar Kühl

La respuesta de @Pete Becker está bien, pero también puedes hacerlo sin pasar la classinstancia como un parámetro explícito function1en C++ 11:

#include <functional>
using namespace std::placeholders;

void function1(std::function<void(int, int)> fun)
{
    fun(1, 1);
}

int main (int argc, const char * argv[])
{
   ...

   aClass a;
   auto fp = std::bind(&aClass::test, a, _1, _2);
   function1(fp);

   return 0;
}
Matt Phillips avatar Sep 30 '2012 16:09 Matt Phillips

Un puntero a una función miembro es diferente de un puntero a una función. Para utilizar una función miembro a través de un puntero, necesita un puntero a ella (obviamente) y un objeto al que aplicarlo. Entonces la versión apropiada de function1sería

void function1(void (aClass::*function)(int, int), aClass& a) {
    (a.*function)(1, 1);
}

y llamarlo:

aClass a; // note: no parentheses; with parentheses it's a function declaration
function1(&aClass::test, a);
Pete Becker avatar Sep 30 '2012 16:09 Pete Becker