Rappel C ++ à l'aide d'un membre de classe

89

Je sais que cela a été demandé tant de fois, et à cause de cela, il est difficile de creuser dans la cruauté et de trouver un exemple simple de ce qui fonctionne.

J'ai ça, c'est simple et ça marche pour 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);
};

Comment cela peut-il être réécrit pour EventHandler::addHandler()fonctionner avec les deux MyClasset YourClass. Je suis désolé mais c'est juste la façon dont mon cerveau fonctionne, j'ai besoin de voir un exemple simple de ce qui fonctionne avant de pouvoir comprendre pourquoi / comment cela fonctionne. Si vous avez une façon préférée de faire ce travail, c'est le moment de le montrer, veuillez marquer ce code et le renvoyer.

[Éditer]

Il a été répondu mais la réponse a été supprimée avant que je puisse donner la coche. La réponse dans mon cas était une fonction basée sur un modèle. Ajout de addHandler à ceci ...

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
la source
4
Qui a publié l'exemple de fonction modèle? Vous avez obtenu la coche, mais vous avez supprimé votre réponse pendant que je testais. Il a fait exactement ce dont j'avais besoin. Un modèle de fonction simple s'est perdu dans le ragoût de toutes les autres informations que je lisais. Votre réponse a été ajoutée en tant que modification à la question.
BentFX
Je pense que c'était JaredC. Vous devrez peut-être le traquer = P
WhozCraig

Réponses:

182

Au lieu d'avoir des méthodes statiques et de transmettre un pointeur vers l'instance de classe, vous pouvez utiliser les fonctionnalités du nouveau standard C ++ 11: std::functionet 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);
        }
};

La addHandlerméthode accepte maintenant unstd::function argument, et cet "objet fonction" n'a pas de valeur de retour et prend un entier comme argument.

Pour le lier à une fonction spécifique, vous utilisez 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;
}

Vous devez utiliser std::bindlors de l'ajout du gestionnaire, car vous devez explicitement spécifier le thispointeur autrement implicite comme argument. Si vous disposez d'une fonction autonome, vous n'avez pas besoin d'utiliser std::bind:

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

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

Le fait que le gestionnaire d'événements utilise des std::functionobjets, permet également d'utiliser les nouvelles fonctions lambda C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Certains programmeur mec
la source
4
Merci Joachim! Cet exemple fait beaucoup pour démystifier std :: function et std :: bind. Je vais certainement l'utiliser à l'avenir! edit Je n'ai toujours pas de lambda du tout :)
BentFX
3
J'ai plié ceci dans mon projet plus large (environ 6 000 lignes. C'est gros pour moi.) Il utilise des vecteurs de définitions de boutons avec des rappels et des paramètres différents puis les alimente en wxWidgets, afin que les objets puissent gérer leurs propres boutons dans le wxFrame. Cela simplifiait beaucoup les choses! Je ne peux pas le dire assez, Internet contient beaucoup trop de technicité et d'opinions, et pas assez d'exemples simples.
BentFX
1
@ user819640 Il n'y a pas de "détachement", à la place std::bindrenvoie simplement un objet (non spécifié), et lorsque vous avez terminé, vous pouvez simplement le laisser sortir de la portée. Si l'objet lié est détruit et que vous essayez d'appeler la fonction, vous obtiendrez un comportement indéfini .
Un mec programmeur du
2
handler->addHandler(), signifie que quelque part vous créez un objet EventHandler? Bonne réponse btw, +1.
gsamaras
1
Notez que vous avez besoin du nombre d'espaces réservés pour correspondre au nombre d'arguments, donc s'il y a deux arguments dans le rappel, vous devez utiliser ...., _1, _2)et ainsi de suite.
Den-Jason
5

Voici une version concise qui fonctionne avec les rappels de méthode de classe et avec les rappels de fonction normaux. Dans cet exemple, pour montrer comment les paramètres sont gérés, la fonction de rappel prend deux paramètres: boolet 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);

Cela limite le code spécifique à C ++ 11 à la méthode addCallback et aux données privées dans la classe Caller. Pour moi, au moins, cela minimise le risque de faire des erreurs lors de sa mise en œuvre.

rsjaffe
la source
3

Ce que vous voulez faire est de créer une interface qui gère ce code et toutes vos classes implémentent l'interface.

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
{

Notez que pour que cela fonctionne, la fonction "Callback" est non statique, ce qui, je crois, est une amélioration. Si vous voulez qu'il soit statique, vous devez le faire comme le suggère JaredC avec des modèles.

Karthik T
la source
Vous n'en montrez qu'un seul côté. Montrez comment déclencher l'événement.
Christopher Pisz
2

Un exemple de travail complet du code ci-dessus .... pour 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 sortie doit être:

Compiler:201103

Handler added...
Result:6
Craig D
la source
1

MyClasset YourClasspourraient tous les deux être dérivés SomeonesClassdont a une Callbackméthode abstraite (virtuelle) . Vous addHandleraccepteriez des objets de type SomeonesClasset MyClasset YourClasspouvez remplacer Callbackpour fournir leur implémentation spécifique du comportement de rappel.

s.bandara
la source
Pour ce que je fais, j'ai joué avec cette idée. Mais à cause du nombre de classes très différentes qui utiliseraient mon gestionnaire, je ne l'ai pas vu comme une option.
BentFX
0

Si vous avez des rappels avec des paramètres différents, vous pouvez utiliser des modèles comme suit:
// compilez avec: 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
la source
1
Pouvez-vous expliquer ce que vous avez fait pour résoudre le problème du PO? Est-il vraiment nécessaire d'inclure le code complet de l'OP? L'OP voulait que son code fonctionne avec le sien YourClass. Vous semblez avoir supprimé cette classe et en avoir ajouté une autre OtherClass. De plus, la question a déjà une réponse bien reçue. Dans quelle mesure votre solution est-elle meilleure pour qu'elle vaille la peine d'être publiée?
klaxonner
Je n'ai pas dit que ma publication était une meilleure solution. J'ai montré comment utiliser "OtherClass" dans un modèle.
mohDady