Devolución de llamada de C ++ usando miembro de clase

89

Sé que esto se ha preguntado tantas veces, y por eso es difícil profundizar y encontrar un ejemplo simple de lo que funciona.

Tengo esto, es simple y funciona para MyClass...

#include <iostream>
using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class EventHandler
{
    public:
        void addHandler(MyClass* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};

EventHandler* handler;

MyClass::MyClass()
{
    private_x = 5;
    handler->addHandler(this);
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << x + instance->private_x << endl;
}

int main(int argc, char** argv)
{
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
}

class YourClass
{
    public:
        YourClass();
        static void Callback(YourClass* instance, int x);
};

¿Cómo se puede reescribir para EventHandler::addHandler()que funcione con ambos MyClassy YourClass. Lo siento, pero así es como funciona mi cerebro, necesito ver un ejemplo simple de lo que funciona antes de poder comprender por qué / cómo funciona. Si tiene una forma favorita de hacer que esto funcione, ahora es el momento de mostrarlo, marque ese código y publíquelo.

[editar]

Se respondió, pero la respuesta se eliminó antes de que pudiera marcar. La respuesta en mi caso fue una función basada en plantilla. Se cambió addHandler a esto ...

class EventHandler
{
    public:
        template<typename T>
        void addHandler(T* owner)
        {
            cout << "Handler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner,1);
        }
};
BentFX
fuente
4
¿Quién publicó el ejemplo de función con plantilla? Obtuvo la marca de verificación, pero eliminó su respuesta mientras estaba probando. Hizo exactamente lo que necesitaba. Una plantilla de función simple se perdió en el guiso de toda la otra información que estaba leyendo. Su respuesta se agregó como editar a la pregunta.
BentFX
Creo que fue JaredC. Puede que
tengas

Respuestas:

182

En lugar de tener métodos estáticos y pasar un puntero a la instancia de la clase, puede usar la funcionalidad en el nuevo estándar C ++ 11: std::function y std::bind:

#include <functional>
class EventHandler
{
    public:
        void addHandler(std::function<void(int)> callback)
        {
            cout << "Handler added..." << endl;
            // Let's pretend an event just occured
            callback(1);
        }
};

El addHandlermétodo ahora acepta un std::functionargumento, y este "objeto de función" no tiene valor de retorno y toma un número entero como argumento.

Para vincularlo a una función específica, usa std::bind:

class MyClass
{
    public:
        MyClass();

        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);
    private:
        int private_x;
};

MyClass::MyClass()
{
    using namespace std::placeholders; // for `_1`

    private_x = 5;
    handler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x)
{
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    cout << x + private_x << endl;
}

Debe usarlo std::bindal agregar el controlador, ya que necesita especificar explícitamente lo implícitothis puntero como argumento. Si tiene una función independiente, no tiene que usar std::bind:

void freeStandingCallback(int x)
{
    // ...
}

int main()
{
    // ...
    handler->addHandler(freeStandingCallback);
}

El hecho de que el controlador de eventos utilice std::functionobjetos también permite utilizar las nuevas funciones lambda de C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Algún tipo programador
fuente
4
¡Gracias Joachim! Este ejemplo hace mucho para desmitificar std :: function y std :: bind. ¡Ciertamente lo usaré en el futuro! editar Todavía no obtengo lambda en absoluto :)
BentFX
3
Incluí esto en mi proyecto más grande (alrededor de 6,000 líneas. Eso es grande para mí). Utiliza vectores de definiciones de botones con diferentes devoluciones de llamada y parámetros y luego lo envía a wxWidgets, para que los objetos puedan administrar sus propios botones en el wxFrame. ¡Esto simplificó mucho las cosas! No puedo decirlo lo suficiente, Internet contiene demasiados tecnicismos y opiniones, y no suficientes ejemplos simples.
BentFX
1
@ user819640 No hay "desvinculación", en su lugar, std::bindsolo devuelve un objeto (no especificado), y cuando haya terminado con él, puede dejarlo fuera de alcance. Si el objeto enlazado se destruye e intenta llamar a la función, obtendrá un comportamiento indefinido .
Un tipo programador
2
handler->addHandler(), significa que en algún lugar creas un objeto EventHandler? Buena respuesta por cierto, +1.
gsamaras
1
Tenga en cuenta que necesita que la cantidad de marcadores de posición coincida con la cantidad de argumentos, por lo que si hay dos argumentos en la devolución de llamada, debe usar ...., _1, _2)y así sucesivamente.
Den-Jason
5

Aquí hay una versión concisa que funciona con devoluciones de llamada de método de clase y con devoluciones de llamada de función regulares. En este ejemplo, para mostrar cómo se manejan los parámetros, la función de devolución de llamada toma dos parámetros: booly int.

class Caller {
  template<class T> void addCallback(T* const object, void(T::* const mf)(bool,int))
  {
    using namespace std::placeholders; 
    callbacks_.emplace_back(std::bind(mf, object, _1, _2));
  }
  void addCallback(void(* const fun)(bool,int)) 
  {
    callbacks_.emplace_back(fun);
  }
  void callCallbacks(bool firstval, int secondval) 
  {
    for (const auto& cb : callbacks_)
      cb(firstval, secondval);
  }
private:
  std::vector<std::function<void(bool,int)>> callbacks_;
}

class Callee {
  void MyFunction(bool,int);
}

//then, somewhere in Callee, to add the callback, given a pointer to Caller `ptr`

ptr->addCallback(this, &Callee::MyFunction);

//or to add a call back to a regular function
ptr->addCallback(&MyRegularFunction);

Esto restringe el código específico de C ++ 11 al método addCallback y los datos privados en la clase Caller. Para mí, al menos, esto minimiza la posibilidad de cometer errores al implementarlo.

rsjaffe
fuente
3

Lo que quiere hacer es crear una interfaz que maneje este código y todas sus clases implementen la interfaz.

class IEventListener{
public:
   void OnEvent(int x) = 0;  // renamed Callback to OnEvent removed the instance, you can add it back if you want.
};


class MyClass :public IEventListener
{
    ...
    void OnEvent(int x); //typically such a function is NOT static. This wont work if it is static.
};

class YourClass :public IEventListener
{

Tenga en cuenta que para que esto funcione, la función "Devolución de llamada" no es estática, lo que creo que es una mejora. Si desea que sea estático, debe hacerlo como sugiere JaredC con plantillas.

Karthik T
fuente
Solo está mostrando un lado. Muestre cómo disparar el evento.
Christopher Pisz
2

Un ejemplo de trabajo completo del código anterior ... para C ++ 11:

#include <stdlib.h>
#include <stdio.h>
#include <functional>

#if __cplusplus <= 199711L
  #error This file needs at least a C++11 compliant compiler, try using:
  #error    $ g++ -std=c++11 ..
#endif

using namespace std;

class EventHandler {
    public:
        void addHandler(std::function<void(int)> callback) {
            printf("\nHandler added...");
            // Let's pretend an event just occured
            callback(1);
        }
};


class MyClass
{
    public:
        MyClass(int);
        // Note: No longer marked `static`, and only takes the actual argument
        void Callback(int x);

    private:
        EventHandler *pHandler;
        int private_x;
};

MyClass::MyClass(int value) {
    using namespace std::placeholders; // for `_1`

    pHandler = new EventHandler();
    private_x = value;
    pHandler->addHandler(std::bind(&MyClass::Callback, this, _1));
}

void MyClass::Callback(int x) {
    // No longer needs an explicit `instance` argument,
    // as `this` is set up properly
    printf("\nResult:%d\n\n", (x+private_x));
}

// Main method
int main(int argc, char const *argv[]) {

    printf("\nCompiler:%ld\n", __cplusplus);
    new MyClass(5);
    return 0;
}


// where $1 is your .cpp file name... this is the command used:
// g++ -std=c++11 -Wall -o $1 $1.cpp
// chmod 700 $1
// ./$1

La salida debe ser:

Compiler:201103

Handler added...
Result:6
Craig D
fuente
1

MyClassy YourClassambos podrían derivarse de lo SomeonesClassque tiene un Callbackmétodo abstracto (virtual) . Su addHandleraceptarían objetos del tipo SomeonesClassy MyClassy YourClasspuede anular Callbackpara proporcionar su aplicación específica del comportamiento de devolución de llamada.

s.bandara
fuente
Por lo que estoy haciendo, jugué con esta idea. Pero debido a la cantidad de clases muy diferentes que usarían mi controlador, no lo vi como una opción.
BentFX
0

Si tiene devoluciones de llamada con diferentes parámetros, puede usar plantillas de la siguiente manera:
// compilar con: g ++ -std = c ++ 11 myTemplatedCPPcallbacks.cpp -o myTemplatedCPPcallbacksApp

#include <functional>     // c++11

#include <iostream>        // due to: cout


using std::cout;
using std::endl;

class MyClass
{
    public:
        MyClass();
        static void Callback(MyClass* instance, int x);
    private:
        int private_x;
};

class OtherClass
{
    public:
        OtherClass();
        static void Callback(OtherClass* instance, std::string str);
    private:
        std::string private_str;
};

class EventHandler
{

    public:
        template<typename T, class T2>
        void addHandler(T* owner, T2 arg2)
        {
            cout << "\nHandler added..." << endl;
            //Let's pretend an event just occured
            owner->Callback(owner, arg2);
         }   

};

MyClass::MyClass()
{
    EventHandler* handler;
    private_x = 4;
    handler->addHandler(this, private_x);
}

OtherClass::OtherClass()
{
    EventHandler* handler;
    private_str = "moh ";
    handler->addHandler(this, private_str );
}

void MyClass::Callback(MyClass* instance, int x)
{
    cout << " MyClass::Callback(MyClass* instance, int x) ==> " 
         << 6 + x + instance->private_x << endl;
}

void OtherClass::Callback(OtherClass* instance, std::string private_str)
{
    cout << " OtherClass::Callback(OtherClass* instance, std::string private_str) ==> " 
         << " Hello " << instance->private_str << endl;
}

int main(int argc, char** argv)
{
    EventHandler* handler;
    handler = new EventHandler();
    MyClass* myClass = new MyClass();
    OtherClass* myOtherClass = new OtherClass();
}
mohDady
fuente
1
¿Puede explicar qué hizo para resolver el problema del OP? ¿Es realmente necesario incluir el código completo del OP? El OP quería que su código funcionara con el suyo YourClass. Parece que eliminó esa clase y agregó una diferente OtherClass. Además, la pregunta ya tiene una respuesta bien recibida. ¿En qué medida es mejor su solución para que valga la pena publicarla?
bocinazo el
No dije que Mi publicación sea una mejor solución. Mostré cómo usar "OtherClass" en una forma de plantilla.
mohDady