C ++ - Rückruf mit Klassenmitglied


88

Ich weiß, dass dies so oft gefragt wurde, und aus diesem Grund ist es schwierig, durch die Kruft zu graben und ein einfaches Beispiel dafür zu finden, was funktioniert.

Ich habe das, es ist einfach und es funktioniert für 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);
};

Wie kann das umgeschrieben werden, so EventHandler::addHandler()wird mit beiden MyClassund funktionieren YourClass. Es tut mir leid, aber es ist nur die Art und Weise, wie mein Gehirn funktioniert. Ich muss ein einfaches Beispiel dafür sehen, was funktioniert, bevor ich verstehen kann, warum / wie es funktioniert. Wenn Sie jetzt eine bevorzugte Methode haben, um diese Funktion zum Vorführen zu bringen, markieren Sie diesen Code und senden Sie ihn zurück.

[bearbeiten]

Es wurde beantwortet, aber die Antwort wurde gelöscht, bevor ich das Häkchen setzen konnte. Die Antwort in meinem Fall war eine Vorlagenfunktion. AddHandler zu diesem geändert ...

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
Wer hat das Beispiel für die Vorlagenfunktion veröffentlicht? Sie haben das Häkchen erhalten, aber Sie haben Ihre Antwort gelöscht, während ich getestet habe. Es hat genau das getan, was ich brauchte. Eine einfache Funktionsvorlage ging im Eintopf aller anderen Informationen, die ich las, verloren. Ihre Antwort wurde als Bearbeitung zur Frage hinzugefügt.
BentFX

Ich denke es war JaredC. Möglicherweise müssen Sie ihn jagen = P
WhozCraig

Antworten:


179

Anstatt statische Methoden zu haben und einen Zeiger auf die Klasseninstanz zu übergeben, können Sie die Funktionalität des neuen C ++ 11-Standards verwenden: std::functionund 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);
        }
};

Die addHandlerMethode akzeptiert jetzt ein std::functionArgument, und dieses "Funktionsobjekt" hat keinen Rückgabewert und verwendet eine Ganzzahl als Argument.

Um es an eine bestimmte Funktion zu binden, verwenden Sie 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;
}

Sie müssen std::bindbeim Hinzufügen des Handlers verwenden, da Sie den ansonsten impliziten thisZeiger explizit als Argument angeben müssen . Wenn Sie eine freistehende Funktion haben, müssen Sie nicht verwenden std::bind:

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

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

Wenn der Ereignishandler std::functionObjekte verwendet, können auch die neuen C ++ 11- Lambda-Funktionen verwendet werden :

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

4
Danke Joachim! Dieses Beispiel entmystifiziert viel std :: function und std :: bind. Ich werde es auf jeden Fall in Zukunft verwenden! edit Ich bekomme immer noch kein Lambda :)
BentFX

3
Ich habe dies in mein größeres Projekt gefaltet (ungefähr 6.000 Zeilen. Das ist groß für mich.) Es verwendet Vektoren von Schaltflächendefinitionen mit unterschiedlichen Rückrufen und Parametern und leitet diese dann an wxWidgets weiter, sodass Objekte ihre eigenen Schaltflächen im wxFrame verwalten können. Das hat die Dinge sehr vereinfacht! Ich kann es nicht genug sagen, das Internet enthält viel zu viel Technik und Meinung und nicht genug einfache Beispiele.
BentFX

1
@ user819640 Es gibt kein "Aufheben der Bindung", sondern es wird std::bindnur ein (nicht angegebenes) Objekt zurückgegeben. Wenn Sie damit fertig sind, können Sie es einfach aus dem Gültigkeitsbereich entfernen . Wenn das gebundene Objekt zerstört wird und Sie versuchen, die Funktion aufzurufen, erhalten Sie ein undefiniertes Verhalten .
Einige Programmierer Typ

2
handler->addHandler()bedeutet, dass Sie irgendwo ein Objekt erstellen von EventHandler? Gute Antwort übrigens +1.
Gsamaras

1
Beachten Sie, dass Sie die Anzahl der Platzhalter benötigen, um mit der Anzahl der Argumente übereinzustimmen. Wenn der Rückruf also zwei Argumente enthält, müssen Sie diese verwenden ...., _1, _2)und so weiter.
Den-Jason

5

Hier ist eine übersichtliche Version, die mit Rückrufen von Klassenmethoden und mit Rückrufen von regulären Funktionen funktioniert. In diesem Beispiel verwendet die Rückruffunktion zwei Parameter, um zu zeigen, wie Parameter behandelt werden: boolund 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);

Dies beschränkt den C ++ 11-spezifischen Code auf die addCallback-Methode und private Daten in der Klasse Caller. Zumindest für mich minimiert dies die Wahrscheinlichkeit von Fehlern bei der Implementierung.


3

Sie möchten eine Schnittstelle erstellen, die diesen Code verarbeitet, und alle Ihre Klassen implementieren die Schnittstelle.

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
{

Beachten Sie, dass die Funktion "Rückruf" nicht statisch ist, was meiner Meinung nach eine Verbesserung darstellt. Wenn Sie möchten, dass es statisch ist, müssen Sie dies tun, wie JaredC es mit Vorlagen vorschlägt.


Sie zeigen nur eine Seite davon. Zeigen Sie, wie das Ereignis ausgelöst wird.
Christopher Pisz

2

Ein vollständiges Arbeitsbeispiel aus dem obigen Code .... für 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

Die Ausgabe sollte sein:

Compiler:201103

Handler added...
Result:6

1

MyClassund YourClassbeide könnten abgeleitet werden, von SomeonesClassdenen eine abstrakte (virtuelle) CallbackMethode hat. Ihr addHandlerwürde Objekte vom Typ akzeptieren SomeonesClassund MyClassund YourClassaußer Kraft setzen können Callbackihre spezifische Implementierung von Callback - Verhalten zu schaffen.


Für das, was ich tue, habe ich mit dieser Idee gespielt. Aber wegen der Anzahl der sehr unterschiedlichen Klassen, die meinen Handler verwenden würden, sah ich es nicht als Option.
BentFX

0

Wenn Sie Rückrufe mit unterschiedlichen Parametern haben, können Sie Vorlagen wie folgt verwenden:
// Kompilieren mit: 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
Können Sie bitte erklären, was Sie getan haben, um das Problem des OP zu lösen? Ist es wirklich notwendig, den vollständigen Code des OP anzugeben? Das OP wollte, dass sein Code mit seinem funktioniert YourClass. Sie scheinen diese Klasse entfernt und eine andere hinzugefügt zu haben OtherClass. Darüber hinaus hat die Frage bereits eine gut aufgenommene Antwort. Inwieweit ist Ihre Lösung besser, sodass es sich lohnt, sie zu veröffentlichen?
Hupen

Ich habe nicht gesagt, dass mein Beitrag eine bessere Lösung ist. Ich habe gezeigt, wie man "OtherClass" als Vorlage verwendet.
Mohady
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.