Обратный вызов C ++ с использованием члена класса

89

Я знаю, что об этом так много раз спрашивали, и из-за этого трудно покопаться в развалинах и найти простой пример того, что работает.

У меня есть такая, она простая и работает на 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);
};

Как это можно переписать, чтобы EventHandler::addHandler()работать с обоими MyClassи YourClass. Прошу прощения, но мой мозг так устроен, мне нужно увидеть простой пример того, что работает, прежде чем я смогу понять, почему и как это работает. Если у вас есть любимый способ сделать эту работу, сейчас самое время показать это, пожалуйста, разметьте этот код и опубликуйте его.

[редактировать]

Был дан ответ, но ответ был удален до того, как я смог поставить галочку. В моем случае ответом была шаблонная функция. Изменен addHandler на это ...

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
источник
4
Кто опубликовал пример шаблонной функции? У вас есть галочка, но вы удалили свой ответ, пока я тестировал. Он сделал именно то, что мне нужно. Простой шаблон функции потерялся среди всей другой информации, которую я читал. Ваш ответ добавлен в качестве редактирования к вопросу.
BentFX 07
Я думаю, это был JaredC. Возможно, вам придется выследить его = P
WhozCraig

Ответы:

182

Вместо использования статических методов и передачи указателя на экземпляр класса вы можете использовать функции нового стандарта C ++ 11: std::functionи 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);
        }
};

Теперь addHandlerметод принимает std::functionаргумент, а этот «объект функции» не имеет возвращаемого значения и принимает целое число в качестве аргумента.

Чтобы привязать его к определенной функции, вы используете 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;
}

Вам нужно использовать std::bindпри добавлении обработчика, так как вам явно нужно указать в противном случае неявный thisуказатель в качестве аргумента. Если у вас есть автономная функция, вам не нужно использовать std::bind:

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

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

Наличие в обработчике событий использования std::functionобъектов также позволяет использовать новые лямбда-функции C ++ 11 :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Какой-то чувак программист
источник
4
Спасибо, Иоахим! Этот пример многое делает для демистификации std :: function и std :: bind. Обязательно воспользуюсь им в будущем! edit Я до сих пор вообще не получаю лямбда :)
BentFX
3
Я вложил это в свой более крупный проект (около 6000 строк. Это большой для меня). Он использует векторы определений кнопок с разными обратными вызовами и параметрами, а затем передает их в wxWidgets, чтобы объекты могли управлять своими собственными кнопками в wxFrame. Это сильно упростило дело! Не могу сказать достаточно, в Интернете слишком много технических подробностей и мнений, а простых примеров мало.
BentFX 07
1
@ user819640 Нет "отвязки", вместо этого std::bindпросто возвращается (неопределенный) объект, и когда вы закончите с ним, вы можете просто отпустить его из области видимости. Если связанный объект разрушен, и вы попытаетесь вызвать функцию, вы получите неопределенное поведение .
Какой-то чувак-программист
2
handler->addHandler(), означает, что где-то вы создаете объект EventHandler? Хороший ответ, кстати, +1.
gsamaras 07
1
Обратите внимание, что вам нужно, чтобы количество заполнителей соответствовало количеству аргументов, поэтому, если в обратном вызове есть два аргумента, которые вам нужно использовать, ...., _1, _2)и так далее.
Ден-Джейсон
5

Вот краткая версия, которая работает с обратными вызовами методов класса и с обратными вызовами обычных функций. В этом примере, чтобы показать, как обрабатываются параметры, функция обратного вызова принимает два параметра: boolи 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);

Это ограничивает специфический для C ++ 11 код методом addCallback и частными данными в классе Caller. Для меня, по крайней мере, это сводит к минимуму вероятность ошибки при его реализации.

rsjaffe
источник
3

Что вы хотите сделать, так это создать интерфейс, который обрабатывает этот код, и все ваши классы реализуют этот интерфейс.

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
{

Обратите внимание, что для того, чтобы это работало, функция «Обратный вызов» нестатична, что, по моему мнению, является улучшением. Если вы хотите, чтобы он был статическим, вам нужно сделать это, как предлагает JaredC, с помощью шаблонов.

Картик Т
источник
Вы показываете только одну его сторону. Покажи, как запустить событие.
Кристофер Пис,
2

Полный рабочий пример из приведенного выше кода .... для 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

Результат должен быть:

Compiler:201103

Handler added...
Result:6
Крейг Д.
источник
1

MyClassи YourClassоба могут быть производными от SomeonesClassабстрактного (виртуального) Callbackметода. Ваш addHandlerбудет принимать объекты типа SomeonesClassи MyClassи YourClassможно переопределить Callbackдля обеспечения их конкретной реализации поведения обратного вызова.

s.bandara
источник
Для того, что я делаю, я поигрался с этой идеей. Но из-за большого количества очень разных классов, которые будут использовать мой обработчик, я не рассматривал это как вариант.
BentFX 07
0

Если у вас есть обратные вызовы с разными параметрами, вы можете использовать следующие шаблоны:
// компилируйте с помощью: 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
источник
1
Не могли бы вы объяснить, что вы сделали для решения проблемы OP? Действительно ли необходимо включать полный код ОП? ОП хотел, чтобы его код работал с его кодом YourClass. Кажется, вы удалили этот класс и добавили другой OtherClass. Более того, на этот вопрос уже есть хорошо принятый ответ. Насколько ваше решение лучше, чтобы его стоило опубликовать?
гудок
Я не сказал, что моя публикация - лучшее решение. Я показал, как использовать "OtherClass" шаблонным способом.
mohDady