Зворотний виклик 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);
        }
};

4
Хто опублікував приклад шаблону функції? Ви отримали галочку, але ви видалили свою відповідь, поки я тестував. Він зробив саме те, що мені потрібно. Простий шаблон функції загубився у тушонці з усією іншою інформацією, яку я читав. Ваша відповідь додана як редагування до запитання.
BentFX

Я думаю, що це був 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об’єкти, також дозволяє використовувати нові лямбда-функції С ++ 11 :

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

4
Дякую Йоакиму! Цей приклад багато робить для демістифікації std :: function та std :: bind. Я, звичайно, буду використовувати його в майбутньому! редагувати Я все ще не отримую
лямбди

3
Я склав це у свій більший проект (близько 6000 рядків. Це велике для мене.) Він використовує вектори визначень кнопок з різними зворотними викликами та параметрами, а потім подає їх у wxWidgets, тому об'єкти можуть керувати своїми власними кнопками у wxFrame. Це значно спростило речі! Я не можу сказати цього достатньо, Інтернет містить занадто багато техніки та думок, а також недостатньо простих прикладів.
BentFX

1
@ user819640 Немає "відв’язування", натомість std::bindпросто повертає (невстановлений) об'єкт, і коли ви закінчите з цим, ви можете просто випустити його з поля дії. Якщо зв'язаний об'єкт буде зруйнований, і ви спробуєте викликати функцію, ви отримаєте невизначену поведінку .
Якийсь програміст, чувак,

2
handler->addHandler(), означає, що десь ви створюєте об'єкт EventHandler? Хороша відповідь до речі, +1.
gsamaras

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. Для мене, принаймні, це зводить до мінімуму можливість помилки при його реалізації.


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для забезпечення їх конкретної реалізації поведінки на зворотній дзвінок.


За те, що я роблю, я погрався з цією ідеєю. Але через кількість дуже різних класів, які використовували б мій обробник, я не розглядав це як варіант.
BentFX

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();
}

1
Чи можете ви пояснити, що ви зробили для вирішення проблеми ОП? Чи справді потрібно включати повний код OP? ОП хотів, щоб його код працював із його YourClass. Здається, ви видалили цей клас і додали інший OtherClass. Більше того, на питання вже добре отримана відповідь. Наскільки ваше рішення краще, щоб його варто було опублікувати?
хонк

Я не сказав, що моє розміщення є кращим рішенням. Я показав, як використовувати "OtherClass" шаблонним способом.
mohDady
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.