Callback C ++ utilizzando il membro della classe


89

So che è stato chiesto così tante volte e per questo motivo è difficile scavare nel cruft e trovare un semplice esempio di ciò che funziona.

Ho questo, è semplice e funziona per 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);
};

Come può essere riscritto così EventHandler::addHandler()funzionerà con entrambi MyClasse YourClass. Mi dispiace ma è solo il modo in cui funziona il mio cervello, ho bisogno di vedere un semplice esempio di ciò che funziona prima di poter capire perché / come funziona. Se hai un modo preferito per farlo funzionare, ora è il momento di metterlo in mostra, contrassegnare quel codice e postarlo di nuovo.

[modificare]

È stato risposto ma la risposta è stata cancellata prima che potessi dare il segno di spunta. La risposta nel mio caso era una funzione basata su modelli. AddHandler modificato in questo ...

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

4
Chi ha pubblicato l'esempio di funzione basato su modelli? Hai ottenuto il segno di spunta, ma hai cancellato la tua risposta durante il test. Ha fatto esattamente quello di cui avevo bisogno. Un semplice modello di funzione si è perso nel miscuglio di tutte le altre informazioni che stavo leggendo. La tua risposta è stata aggiunta come modifica alla domanda.
BentFX

Penso che fosse JaredC. Potrebbe essere necessario dargli la caccia = P
WhozCraig

Risposte:


182

Invece di avere metodi statici e passare un puntatore all'istanza della classe, potresti usare la funzionalità nel nuovo standard C ++ 11: std::functione 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);
        }
};

Il addHandlermetodo ora accetta un std::functionargomento e questo "oggetto funzione" non ha valore di ritorno e accetta un numero intero come argomento.

Per associarlo a una funzione specifica, usi 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;
}

È necessario utilizzare std::bindquando si aggiunge il gestore, poiché è necessario specificare esplicitamente il thispuntatore altrimenti implicito come argomento. Se hai una funzione free-standing, non devi usare std::bind:

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

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

Avere il gestore di eventi che usa std::functionoggetti, rende anche possibile usare le nuove funzioni lambda C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });

4
Grazie Joachim! Questo esempio fa molto per demistificare std :: function e std :: bind. Lo userò sicuramente in futuro! modifica Continuo a non ricevere lambda :)
BentFX

3
L'ho piegato nel mio progetto più grande (circa 6.000 righe. È grande per me.) Utilizza vettori di definizioni di pulsanti con callback e parametri diversi, quindi li invia a wxWidgets, in modo che gli oggetti possano gestire i propri pulsanti in wxFrame. Questo ha semplificato molto le cose! Non posso dirlo abbastanza, Internet contiene troppi tecnicismi e opinioni e non abbastanza esempi semplici.
BentFX

1
@ user819640 Non c'è "unbind", invece std::bindrestituisce solo un oggetto (non specificato), e quando hai finito puoi semplicemente lasciarlo fuori campo. Se l'oggetto associato viene distrutto e provi a chiamare la funzione, otterrai un comportamento indefinito .
Un tizio programmatore il

2
handler->addHandler(), significa che da qualche parte crei un oggetto EventHandler? Buona risposta btw, +1.
gsamaras

1
Si noti che è necessario che il numero di segnaposto corrisponda al numero di argomenti, quindi se ci sono due argomenti nel callback è necessario utilizzare ...., _1, _2)e così via.
Den-Jason

5

Ecco una versione concisa che funziona con callback di metodi di classe e con callback di funzioni regolari. In questo esempio, per mostrare come vengono gestiti i parametri, la funzione di callback accetta due parametri: boole 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);

Ciò limita il codice specifico di C ++ 11 al metodo addCallback e ai dati privati ​​nella classe Caller. Per me, almeno, questo riduce al minimo la possibilità di commettere errori durante l'implementazione.


3

Quello che vuoi fare è creare un'interfaccia che gestisca questo codice e tutte le tue classi implementino l'interfaccia.

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
{

Si noti che per far funzionare la funzione "Callback" non è statica, il che credo sia un miglioramento. Se vuoi che sia statico, devi farlo come suggerisce JaredC con i modelli.


Stai mostrando solo un lato di esso. Mostra come attivare l'evento.
Christopher Pisz

2

Un esempio funzionante completo dal codice sopra ... per 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

L'output dovrebbe essere:

Compiler:201103

Handler added...
Result:6

1

MyClasse YourClasspotrebbero essere entrambi derivati ​​da SomeonesClasscui ha un Callbackmetodo astratto (virtuale) . Il tuo addHandleraccetterebbero oggetti di tipo SomeonesClasse di MyClasse YourClasspuò ignorare Callbackper fornire la loro attuazione specifica del comportamento di callback.


Per quello che sto facendo ho giocato con questa idea. Ma a causa del numero di classi molto diverse che avrebbero utilizzato il mio gestore, non l'ho visto come un'opzione.
BentFX

0

Se disponi di callback con parametri diversi, puoi utilizzare i modelli come segue:
// compila 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();
}

1
Puoi spiegare cosa hai fatto per risolvere il problema dell'OP? È davvero necessario includere il codice completo dell'OP? L'OP voleva che il suo codice funzionasse con il suo YourClass. Sembra che tu abbia rimosso quella classe e ne hai aggiunta una diversa OtherClass. Inoltre, la domanda ha già una risposta ben accolta. In che misura la tua soluzione è migliore in modo che valga la pena pubblicare?
clacson

Non ho detto che il mio intervento è una soluzione migliore. Ho mostrato come utilizzare "OtherClass" in un modo modello.
mohDady
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.