web-dev-qa-db-fra.com

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

Je sais que cela a été demandé à maintes reprises, et à cause de cela, il est difficile de creuser et de trouver un exemple simple de ce qui fonctionne.

J'ai ceci, 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 que EventHandler::addHandler() fonctionne à la fois avec MyClass et 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 maintenant, c'est le moment de le montrer, veuillez biffer ce code et le poster.

[modifier]

La réponse a été donnée, mais la réponse a été supprimée avant que je puisse cocher la case… .. La réponse dans mon cas était une fonction basée sur un modèle. Changé addHandler à cette ...

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

Au lieu d'avoir des méthodes statiques et de passer un pointeur sur l'instance de la classe, vous pouvez utiliser les fonctionnalités du nouveau standard C++ 11: std::function et 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 méthode addHandler accepte désormais un argument std::function, 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::bind lors de l'ajout du gestionnaire, car vous devez explicitement spécifier le pointeur this par ailleurs implicite en tant qu'argument. Si vous avez une fonction indépendante, vous n'avez pas besoin d'utiliser std::bind:

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

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

L'utilisation par le gestionnaire d'événements des objets std::function permet également d'utiliser les nouvelles fonctions C++ 11 lambda :

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

Voici une version concise qui fonctionne avec les rappels de méthodes de classe et avec les rappels de fonctions standard. Dans cet exemple, pour montrer comment les paramètres sont gérés, la fonction de rappel prend deux paramètres: bool et 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 de la classe Caller. Pour moi, au moins, cela minimise le risque de faire des erreurs lors de sa mise en œuvre.

3
rsjaffe

Ce que vous voulez faire, c'est créer une interface qui gère ce code et que 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 i est une amélioration. Si vous voulez que ce soit statique, vous devez le faire comme JaredC le suggère avec des modèles.

3
Karthik T

MyClass et YourClass peuvent tous deux être dérivés de SomeonesClass qui a une méthode abstraite (virtuelle) Callback. Votre addHandler accepterait des objets de type SomeonesClass et MyClass et YourClass peuvent remplacer Callback pour fournir leur implémentation spécifique du comportement de rappel.

1
s.bandara

Un exemple de travail complet à partir 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 devrait être:

Compiler:201103

Handler added...
Result:6
0
Craig D