Sınıf üyesini kullanarak C ++ geri arama


92

Bunun pek çok kez sorulduğunu biliyorum ve bu nedenle, püf noktasını kazmak ve neyin işe yaradığına dair basit bir örnek bulmak zor.

Bunu anladım, basit ve işe yarıyor 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);
};

Bu nasıl yeniden yazılabilir, böylece EventHandler::addHandler()hem MyClassve hem de ile çalışacaktır YourClass. Üzgünüm ama beynimin çalışma şekli bu, neden / nasıl çalıştığını anlamadan önce neyin işe yaradığına dair basit bir örnek görmem gerekiyor. Bu işi yapmak için favori bir yolunuz varsa, şimdi onu gösterme zamanı, lütfen bu kodu işaretleyin ve geri gönderin.

[Düzenle]

Yanıtlandı ancak ben onay işaretini veremeden yanıt silindi. Benim durumumdaki cevap şablonlu bir işlevdi. AddHandler buna değiştirildi ...

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
Şablonlu işlev örneğini kim yayınladı? Onay işaretini aldın, ama ben test ederken cevabını sildin. Tam olarak ihtiyacım olanı yaptı. Okuduğum diğer tüm bilgilerin güvende olduğu basit bir işlev şablonu kayboldu. Cevabınız soruya düzenleme olarak eklendi.
BentFX

Sanırım o JaredC. Onu
avlamanız gerekebilir

Yanıtlar:


186

Statik yöntemlere sahip olmak ve sınıf örneğine bir işaretçi iletmek yerine, yeni C ++ 11 standardındaki işlevselliği kullanabilirsiniz: std::functionve 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);
        }
};

addHandlerYöntemi şimdi bir kabul std::functionbağımsız değişken ve bu "fonksiyonu nesne" herhangi bir geri dönüş değeri ve değişken olarak bir tam sayı alır.

Bunu belirli bir işleve bağlamak için şunları kullanırsınız 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::bindAksi takdirde örtük thisişaretçiyi bir bağımsız değişken olarak açıkça belirtmeniz gerektiğinden, işleyiciyi eklerken kullanmanız gerekir . Serbest duran bir işleviniz varsa, kullanmak zorunda değilsiniz std::bind:

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

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

Olay işleyicisinin std::functionnesneleri kullanması, yeni C ++ 11 lambda işlevlerinin kullanılmasını da mümkün kılar :

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

4
Joachim teşekkürler! Bu örnek std :: function ve std :: bind'in gizemini çözmek için çok şey yapar. Kesinlikle gelecekte kullanacağım! düzenle Hâlâ lambda alamıyorum :)
BentFX

3
Bunu daha büyük projeme katladım (yaklaşık 6.000 satır. Bu benim için büyük.) Farklı geri çağırmalara ve parametrelere sahip düğme tanımlarının vektörlerini kullanır ve ardından bunları wxWidgets'a besler, böylece nesneler wxFrame'de kendi düğmelerini yönetebilir. Bu işleri çok kolaylaştırdı! Yeterince söyleyemem, internet çok fazla teknik bilgi ve görüş içeriyor ve yeterince basit örnek yok.
BentFX

1
@ user819640 "Bağlantının kaldırılması" yoktur, bunun yerine std::bind(belirtilmemiş) bir nesne döndürür ve işiniz bittiğinde kapsam dışına çıkmasına izin verebilirsiniz. Bağlı nesne yok edilirse ve işlevi çağırmaya çalışırsanız, tanımsız davranışla karşılaşırsınız .
Programcı biri

2
handler->addHandler(), bir yerde bir nesne oluşturduğunuz anlamına EventHandlermı geliyor? İyi cevap btw, +1.
gsamaras

2
Bağımsız değişkenlerin sayısıyla eşleşecek yer tutucuların sayısına ihtiyacınız olduğuna dikkat edin, bu nedenle geri aramada iki bağımsız değişken varsa kullanmanız gerekir, ...., _1, _2)vb.
Den-Jason

6

Sınıf yöntemi geri aramaları ve normal işlev geri aramaları ile çalışan kısa bir sürümü burada bulabilirsiniz. Bu örnekte, parametrelerin nasıl işlendiğini göstermek için geri çağırma işlevi iki parametre alır: boolve 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);

Bu, C ++ 11'e özgü kodu addCallback yöntemiyle ve Caller sınıfındaki özel verilerle sınırlar. En azından bana göre bu, uygularken hata yapma olasılığını en aza indiriyor.


4

Yapmak istediğiniz, bu kodu işleyen ve tüm sınıflarınızın bu arayüzü uygulayan bir arayüz oluşturmaktır.

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
{

Bunun çalışması için "Geri arama" işlevinin durağan olmadığını ve bunun bir gelişme olduğuna inanıyorum . Statik olmasını istiyorsanız, bunu JaredC'nin şablonlarda önerdiği şekilde yapmanız gerekir.


Sen sadece bir tarafını gösteriyorsun. Olayı nasıl tetikleyeceğinizi gösterin.
Christopher Pisz

3

Yukarıdaki koddan eksiksiz bir çalışma örneği .... C ++ 11 için:

#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

Çıktı şöyle olmalıdır:

Compiler:201103

Handler added...
Result:6

1

MyClassve YourClassher ikisi de SomeonesClasssoyut (sanal) bir Callbackyönteme sahip olanlardan türetilebilir . Sizin addHandlertüründeki nesneleri kabul edeceğini SomeonesClassve MyClassve YourClassgeçersiz kılabilir Callbackgeri arama davranışının kendi özel uygulamasını sağlamak.


Yaptığım şey için bu fikirle oynadım. Ancak işleyicimi kullanacak çok farklı sınıfların sayısı nedeniyle bunu bir seçenek olarak görmedim.
BentFX

0

Farklı parametrelere sahip geri aramalarınız varsa, şablonları aşağıdaki gibi kullanabilirsiniz:
// şununla derleyin: 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'nin problemini çözmek için ne yaptığınızı açıklayabilir misiniz? OP'nin tam kodunu dahil etmek gerçekten gerekli mi? OP, kodunun kendisininkiyle çalışmasını istedi YourClass. Görünüşe göre bu sınıfı kaldırmış ve farklı bir sınıf eklemişsiniz OtherClass. Dahası, sorunun zaten iyi alınmış bir cevabı var. Göndermeye değer olması için çözümünüz ne kadar iyi?
honk

Gönderimin daha iyi bir çözüm olduğunu söylemedim. "OtherClass" ın şablon olarak nasıl kullanılacağını gösterdim.
mohDady
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.