C ++ wywołanie zwrotne przy użyciu elementu członkowskiego klasy

89

Wiem, że zadawano to wiele razy i dlatego trudno jest przekopać się przez skorupę i znaleźć prosty przykład tego, co działa.

Mam to, to proste i działa przez 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);
};

Jak można to przepisać, aby EventHandler::addHandler()działało zarówno z, jak MyClassi YourClass. Przykro mi, ale tak działa mój mózg. Muszę zobaczyć prosty przykład tego, co działa, zanim zrozumiem, dlaczego / jak to działa. Jeśli masz ulubiony sposób, aby to zadziałać, teraz jest czas, aby się nim pochwalić, oznacz ten kod i prześlij go z powrotem.

[edytować]

Otrzymałem odpowiedź, ale odpowiedź została usunięta, zanim mogłem zaznaczyć znacznik wyboru. Odpowiedzią w moim przypadku była funkcja szablonowa. Zmieniono addHandler na to ...

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
źródło
4
Kto opublikował przykład funkcji na podstawie szablonu? Masz znacznik wyboru, ale skasowałeś swoją odpowiedź podczas testu. Zrobił dokładnie to, czego potrzebowałem. Prosty szablon funkcji zagubił się w gulaszu wszystkich innych informacji, które czytałem. Twoja odpowiedź została dodana jako edycja pytania.
BentFX,
Myślę, że to był JaredC. Być może będziesz musiał go upolować = P
WhozCraig

Odpowiedzi:

182

Zamiast mieć statyczne metody i przekazywać wskaźnik do instancji klasy, możesz użyć funkcjonalności w nowym standardzie C ++ 11: std::functioni 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);
        }
};

addHandlerMetody się przyjmuje std::functionargumentu, i to „obiektu funkcji” nie mają wartość powrotną i zajmuje całkowitą jako argument.

Aby powiązać go z określoną funkcją, użyj 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;
}

Musisz użyć std::bindpodczas dodawania procedury obsługi, ponieważ jawnie musisz określić niejawny thiswskaźnik w przeciwnym razie jako argument. Jeśli masz funkcję wolnostojącą, nie musisz używać std::bind:

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

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

Posiadanie obsługi zdarzeń używa std::functionobiektów, umożliwia również korzystanie z nowych funkcji lambda C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Jakiś koleś programista
źródło
4
Dzięki Joachim! Ten przykład robi wiele, aby zdemistyfikować std :: function i std :: bind. Z pewnością użyję go w przyszłości! edit Nadal nie mam lambdy :)
BentFX,
3
Złożyłem to do mojego większego projektu (około 6000 linii. To dla mnie duże). Używa wektorów definicji przycisków z różnymi wywołaniami zwrotnymi i parametrami, a następnie przekazuje je do wxWidgets, więc obiekty mogą zarządzać swoimi przyciskami w wxFrame. To bardzo uprościło sprawę! Nie mogę powiedzieć tego wystarczająco dużo, internet zawiera o wiele za dużo szczegółów technicznych i opinii, a za mało prostych przykładów.
BentFX,
1
@ user819640 Nie ma "unbind", zamiast tego std::bindpo prostu zwraca (nieokreślony) obiekt, a kiedy skończysz, możesz po prostu pozwolić mu wyjść poza zakres. Jeśli powiązany obiekt zostanie zniszczony i spróbujesz wywołać funkcję, otrzymasz niezdefiniowane zachowanie .
Jakiś programista,
2
handler->addHandler()oznacza, że ​​gdzieś tworzysz obiekt EventHandler? Dobra odpowiedź przy okazji, +1.
gsamaras
1
Zwróć uwagę, że potrzebujesz liczby symboli zastępczych, aby dopasować liczbę argumentów, więc jeśli w wywołaniu zwrotnym są dwa argumenty, których musisz użyć, ...., _1, _2)i tak dalej.
Den-Jason
5

Oto zwięzła wersja, która działa z wywołaniami zwrotnymi metod klas i zwykłymi wywołaniami zwrotnymi funkcji. W tym przykładzie, aby pokazać, jak obsługiwane są parametry, funkcja zwrotna przyjmuje dwa parametry: booli 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);

Ogranicza to kod specyficzny dla C ++ 11 do metody addCallback i prywatnych danych w klasie Caller. Przynajmniej według mnie minimalizuje to ryzyko popełnienia błędów podczas wdrażania.

rsjaffe
źródło
3

To, co chcesz zrobić, to stworzyć interfejs, który obsługuje ten kod i wszystkie twoje klasy implementują interfejs.

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
{

Zauważ, że aby to zadziałało, funkcja „Callback” nie jest statyczna, co uważam za ulepszenie. Jeśli chcesz, aby był statyczny, musisz to zrobić tak, jak sugeruje JaredC za pomocą szablonów.

Karthik T
źródło
Pokazujesz tylko jedną stronę. Pokaż, jak odpalić wydarzenie.
Christopher Pisz
2

Kompletny przykład roboczy z powyższego kodu ... dla 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

Wynik powinien być:

Compiler:201103

Handler added...
Result:6
Craig D.
źródło
1

MyClassi YourClassoba mogą pochodzić z metody SomeonesClassabstrakcyjnej (wirtualnej) Callback. Twój addHandlerprzyjąłby obiektów typu SomeonesClassa MyClassi YourClassmoże zastąpić Callback, aby zapewnić ich realizację konkretnego zachowania zwrotnego.

s.bandara
źródło
Bo to, co robię, bawiłam się tym pomysłem. Ale ze względu na liczbę bardzo różnych klas, które używałyby mojego handlera, nie widziałem tego jako opcji.
BentFX,
0

Jeśli masz wywołania zwrotne z różnymi parametrami, możesz użyć szablonów w następujący sposób:
// skompiluj z: 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
źródło
1
Czy możesz wyjaśnić, co zrobiłeś, aby rozwiązać problem OP? Czy naprawdę konieczne jest dołączenie pełnego kodu PO? OP chciał, aby jego kod działał z jego YourClass. Wygląda na to, że usunąłeś tę klasę i dodałeś inną OtherClass. Co więcej, to pytanie ma już dobrze przyjętą odpowiedź. W jakim stopniu Twoje rozwiązanie jest lepsze, aby warto było je publikować?
trąbka
Nie powiedziałem, że moje publikowanie jest lepszym rozwiązaniem. Pokazałem, jak używać „OtherClass” w szablonowy sposób.
mohDady