Callback C ++ usando um membro da classe

89

Sei que isso foi perguntado tantas vezes e, por causa disso, é difícil cavar no meio do nada e encontrar um exemplo simples do que funciona.

Eu tenho isso, é simples e 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);
};

Como isso pode ser reescrito para EventHandler::addHandler()que funcione com MyClasse YourClass. Sinto muito, mas é apenas a maneira como meu cérebro funciona. Preciso ver um exemplo simples do que funciona antes de compreender por que / como funciona. Se você tem uma maneira favorita de fazer isso funcionar, agora é a hora de exibi-la, marque esse código e poste-o de volta.

[editar]

Ele foi respondido, mas a resposta foi excluída antes que eu pudesse marcar. A resposta no meu caso foi uma função modelada. AddHandler alterado para ...

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
fonte
4
Quem postou o exemplo de função com modelo? Você marcou, mas excluiu sua resposta enquanto eu estava testando. Fez exatamente o que eu precisava. Um modelo de função simples se perdeu na mistura de todas as outras informações que eu estava lendo. Sua resposta adicionada como edição à pergunta.
BentFX
Acho que foi JaredC. Você pode precisar caçá-lo = P
WhozCraig

Respostas:

181

Em vez de ter métodos estáticos e passar um ponteiro para a instância da classe, você pode usar a funcionalidade no novo padrão 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);
        }
};

O addHandlermétodo agora aceita um std::functionargumento e este "objeto de função" não tem valor de retorno e leva um inteiro como argumento.

Para vinculá-lo a uma função específica, você 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;
}

Você precisa usar std::bindao adicionar o manipulador, pois você precisa especificar explicitamente o thisponteiro implícito como um argumento. Se você tem uma função independente, não precisa usar std::bind:

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

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

O fato de o manipulador de eventos usar std::functionobjetos também torna possível usar as novas funções lambda do C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Algum programador cara
fonte
4
Obrigado Joachim! Este exemplo faz muito para desmistificar std :: function e std :: bind. Com certeza vou usá-lo no futuro! editar Eu ainda não entendi lambda :)
BentFX
3
Dobrei isso em meu projeto maior (cerca de 6.000 linhas. Isso é grande para mim.) Ele usa vetores de definições de botão com diferentes retornos de chamada e parâmetros, em seguida, os alimenta para wxWidgets, para que os objetos possam gerenciar seus próprios botões no wxFrame. Isso simplificou muito as coisas! Não posso dizer o suficiente, a Internet contém demasiados detalhes técnicos e opiniões, e não exemplos simples suficientes.
BentFX
1
@ user819640 Não há "desvincular", em vez disso, std::bindapenas retorna um objeto (não especificado) e, quando terminar, pode simplesmente deixá-lo sair do escopo. Se o objeto vinculado for destruído e você tentar chamar a função, obterá um comportamento indefinido .
Algum programador
2
handler->addHandler(), significa que em algum lugar você cria um objeto EventHandler? Boa resposta aliás, +1.
gsamaras
1
Observe que você precisa do número de marcadores para corresponder ao número de argumentos, portanto, se houver dois argumentos no retorno de chamada, você precisará usar ...., _1, _2)e assim por diante.
Den-Jason
5

Esta é uma versão concisa que funciona com retornos de chamada de método de classe e com retornos de chamada de função regulares. Neste exemplo, para mostrar como os parâmetros são tratados, a função de retorno de chamada usa dois parâmetros: 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);

Isso restringe o código específico do C ++ 11 ao método addCallback e aos dados privados na classe Caller. Para mim, pelo menos, isso minimiza a chance de cometer erros na implementação.

rsjaffe
fonte
3

O que você quer fazer é fazer uma interface que lida com esse código e todas as suas classes implementam a 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
{

Observe que, para que isso funcione, a função "Callback" não é estática, o que acredito ser uma melhoria. Se você quiser que seja estático, você precisa fazer isso como o JaredC sugere com modelos.

Karthik T
fonte
Você está mostrando apenas um lado dele. Mostre como disparar o evento.
Christopher Pisz
2

Um exemplo funcional completo do código acima ... 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

A saída deve ser:

Compiler:201103

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

MyClasse YourClassambos podem ser derivados de SomeonesClassum Callbackmétodo abstrato (virtual) . Seu addHandleraceitaria objetos do tipo SomeonesClasse MyClasse YourClasspode substituir Callbacka fornecer sua implementação específica do comportamento de retorno de chamada.

s.bandara
fonte
Pelo que estou fazendo, brinquei com essa ideia. Mas, devido ao número de classes amplamente diferentes que estariam usando meu manipulador, não vi isso como uma opção.
BentFX
0

Se você tiver retornos de chamada com parâmetros diferentes, poderá usar os modelos da seguinte maneira:
// compilar com: 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
fonte
1
Você pode explicar o que você fez para resolver o problema do OP? É realmente necessário incluir o código completo do OP? O OP queria que seu código funcionasse com o seu YourClass. Você parece ter removido aquela classe e adicionado uma diferente OtherClass. Além disso, a pergunta já teve uma resposta bem recebida. Até que ponto a sua solução é melhor para que valha a pena postar?
buzina em
Eu não disse que minha postagem é a melhor solução. Mostrei como usar "OtherClass" como um modelo.
mohDady,