C ++ callback menggunakan anggota kelas

89

Saya tahu ini telah ditanyakan berkali-kali, dan karena itu sulit untuk menggali lebih dalam dan menemukan contoh sederhana tentang apa yang berhasil.

Saya punya ini, sederhana dan berfungsi untuk 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);
};

Bagaimana itu bisa ditulis ulang sehingga EventHandler::addHandler()akan bekerja dengan MyClassdan YourClass. Maaf, tapi ini hanya cara otak saya bekerja, saya perlu melihat contoh sederhana tentang apa yang berhasil sebelum saya dapat memahami mengapa / bagaimana cara kerjanya. Jika Anda punya cara favorit untuk membuat ini berfungsi, sekaranglah waktunya untuk memamerkannya, tandai kode itu dan kirim kembali.

[edit]

Itu dijawab tetapi jawabannya telah dihapus sebelum saya dapat memberikan tanda centang. Jawaban dalam kasus saya adalah fungsi template. Mengubah addHandler menjadi ini ...

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
sumber
4
Siapa yang memposting contoh fungsi template? Anda mendapat tanda centang, tetapi Anda menghapus jawaban Anda saat saya menguji. Itu melakukan apa yang saya butuhkan. Template fungsi sederhana hilang dalam rebusan semua info lain yang saya baca. Jawaban Anda ditambahkan sebagai edit pada pertanyaan.
BentFX
Saya pikir itu JaredC. Anda mungkin perlu memburunya = P
WhozCraig

Jawaban:

182

Alih-alih memiliki metode statis dan meneruskan pointer ke instance kelas, Anda dapat menggunakan fungsionalitas dalam standar C ++ 11 yang baru: std::functiondan 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);
        }
};

The addHandlerMetode sekarang menerima std::functionargumen, dan ini "fungsi objek" tidak memiliki nilai kembali dan mengambil integer sebagai argumen.

Untuk mengikatnya ke fungsi tertentu, Anda menggunakan 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;
}

Anda perlu menggunakan std::bindsaat menambahkan handler, karena Anda secara eksplisit perlu menentukan thispointer implisit sebagai argumen. Jika Anda memiliki fungsi yang berdiri sendiri, Anda tidak perlu menggunakan std::bind:

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

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

Memiliki event handler menggunakan std::functionobjek, juga memungkinkan untuk menggunakan fungsi lambda C ++ 11 yang baru :

handler->addHandler([](int x) { std::cout << "x is " << x << '\n'; });
Beberapa programmer, bung
sumber
4
Terima kasih Joachim! Contoh ini melakukan banyak hal untuk mengungkap std :: function dan std :: bind. Saya pasti akan menggunakannya di masa depan! sunting Saya masih tidak mendapatkan lambda sama sekali :)
BentFX
3
Saya melipat ini ke dalam proyek saya yang lebih besar (sekitar 6.000 baris. Itu besar bagi saya.) Ini menggunakan vektor definisi tombol dengan callback dan parameter yang berbeda kemudian memasukkannya ke wxWidgets, sehingga objek dapat mengelola tombol mereka sendiri di wxFrame. Hal ini sangat menyederhanakan! Saya tidak bisa mengatakannya cukup, internet mengandung terlalu banyak teknis dan opini, dan tidak cukup contoh sederhana.
BentFX
1
@ user819640 Tidak ada "unbind", sebagai gantinya std::bindhanya mengembalikan objek (tidak ditentukan), dan ketika Anda selesai dengannya Anda bisa membiarkannya keluar dari ruang lingkup. Jika objek terikat dihancurkan dan Anda mencoba memanggil fungsi tersebut, Anda akan mendapatkan perilaku tidak terdefinisi .
Beberapa programmer dude
2
handler->addHandler(), berarti di suatu tempat Anda membuat objek EventHandler? Jawaban bagus btw, +1.
gsamaras
1
Perhatikan bahwa Anda memerlukan jumlah placeholder untuk mencocokkan dengan jumlah argumen, jadi jika ada dua argumen dalam callback yang perlu Anda gunakan ...., _1, _2)dan seterusnya.
Den-Jason
5

Berikut adalah versi ringkas yang berfungsi dengan callback metode kelas dan dengan callback fungsi reguler. Dalam contoh ini, untuk menunjukkan bagaimana parameter ditangani, fungsi callback mengambil dua parameter: booldan 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);

Ini membatasi kode khusus C ++ 11 ke metode addCallback dan data pribadi di kelas Pemanggil. Bagi saya, setidaknya, ini meminimalkan kemungkinan membuat kesalahan saat menerapkannya.

rsjaffe.dll
sumber
3

Apa yang ingin Anda lakukan adalah membuat antarmuka yang menangani kode ini dan semua kelas Anda mengimplementasikan antarmuka tersebut.

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
{

Perhatikan bahwa agar ini berfungsi, fungsi "Callback" adalah non-statis yang menurut saya merupakan peningkatan. Jika Anda ingin menjadi statis, Anda perlu melakukannya seperti yang disarankan JaredC dengan template.

Karthik T
sumber
Anda hanya menampilkan satu sisi saja. Tunjukkan cara mengaktifkan acara tersebut.
Christopher Pisz
2

Contoh kerja lengkap dari kode di atas .... untuk 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

Output harus:

Compiler:201103

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

MyClassdan YourClasskeduanya bisa diturunkan SomeonesClassyang memiliki metode abstrak (virtual) Callback. Anda addHandlerakan menerima objek bertipe SomeonesClassdan MyClassdan YourClassdapat menimpa Callbackuntuk menyediakan implementasi spesifik dari perilaku callback.

s.bandara
sumber
Untuk apa yang saya lakukan, saya mempermainkan ide ini. Tetapi karena banyaknya kelas yang berbeda yang akan menggunakan penangan saya, saya tidak melihatnya sebagai opsi.
BentFX
0

Jika Anda memiliki callback dengan parameter berbeda, Anda dapat menggunakan template sebagai berikut:
// kompilasi dengan: 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
sumber
1
Bisakah Anda menjelaskan apa yang Anda lakukan untuk menyelesaikan masalah OP? Apakah benar-benar perlu menyertakan kode lengkap OP? OP ingin kodenya bekerja dengannya YourClass. Anda tampaknya telah menghapus kelas itu dan menambahkan yang berbeda OtherClass. Apalagi pertanyaan itu sudah mendapat jawaban yang baik. Sejauh mana solusi Anda lebih baik sehingga layak untuk diposting?
Bunyikan klakson
Saya tidak mengatakan bahwa posting saya adalah solusi yang lebih baik. Saya menunjukkan bagaimana menggunakan "OtherClass" dengan cara template.
mohDady