การโทรกลับ C ++ โดยใช้สมาชิกคลาส


92

ฉันรู้ว่ามีการถามเรื่องนี้หลายครั้งและด้วยเหตุนี้จึงเป็นการยากที่จะขุดผ่านปมและหาตัวอย่างง่ายๆว่าอะไรได้ผล

ฉันมีสิ่งนี้มันง่ายและใช้ได้กับ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

คำตอบ:


186

แทนที่จะมีวิธีการแบบคงที่และส่งผ่านตัวชี้ไปยังอินสแตนซ์คลาสคุณสามารถใช้ฟังก์ชันการทำงานในมาตรฐาน 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
ขอบคุณ Joachim! ตัวอย่างนี้ช่วยให้เข้าใจ std :: function และ std :: bind ได้มาก แน่นอนฉันจะใช้มันในอนาคต! แก้ไขยังไม่ได้แลม
ด้า

3
ฉันพับมันเป็นโปรเจ็กต์ขนาดใหญ่ของฉัน (ประมาณ 6,000 บรรทัดมันใหญ่สำหรับฉัน) มันใช้เวกเตอร์ของคำจำกัดความของปุ่มที่มีการเรียกกลับและพารามิเตอร์ที่แตกต่างกันจากนั้นฟีดข้อมูลนั้นไปยัง wxWidgets เพื่อให้ออบเจ็กต์สามารถจัดการปุ่มของตัวเองใน wxFrame ได้ สิ่งนี้ทำให้ง่ายขึ้นมาก! ฉันไม่สามารถพูดได้เพียงพออินเทอร์เน็ตมีเทคนิคและความคิดเห็นมากเกินไปและมีตัวอย่างง่ายๆไม่เพียงพอ
BentFX

1
@ user819640 ไม่มีการ "เลิกผูก" แต่std::bindเพียงส่งคืนวัตถุ (ไม่ระบุ) และเมื่อคุณทำเสร็จแล้วคุณก็สามารถปล่อยมันออกไปนอกขอบเขต ถ้าวัตถุที่ถูกผูกไว้เป็น destructed และคุณพยายามที่จะเรียกฟังก์ชั่นที่คุณจะได้รับพฤติกรรมไม่ได้กำหนด
เพื่อนโปรแกรมเมอร์บางคน

2
handler->addHandler()หมายความว่าที่ไหนสักแห่งที่คุณสร้างวัตถุEventHandler? คำตอบที่ดี btw, +1
gsamaras

2
โปรดทราบว่าคุณต้องมีจำนวนตัวยึดตำแหน่งเพื่อให้ตรงกับจำนวนอาร์กิวเมนต์ดังนั้นหากมีอาร์กิวเมนต์สองรายการในการเรียกกลับคุณต้องใช้...., _1, _2)และอื่น ๆ
Den-Jason

6

นี่คือเวอร์ชันที่กระชับซึ่งทำงานร่วมกับการเรียกกลับเมธอดคลาสและการเรียกกลับของฟังก์ชันปกติ ในตัวอย่างนี้แสดงให้เห็นว่าพารามิเตอร์ได้รับการจัดการฟังก์ชันการเรียกกลับจะใช้เวลาสองพารามิเตอร์และboolint

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 สำหรับฉันอย่างน้อยสิ่งนี้ช่วยลดโอกาสที่จะเกิดข้อผิดพลาดเมื่อนำไปใช้


4

สิ่งที่คุณต้องการทำคือสร้างอินเทอร์เฟซที่จัดการโค้ดนี้และคลาสทั้งหมดของคุณใช้อินเทอร์เฟซ

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
{

โปรดทราบว่าในการทำงานฟังก์ชัน "Callback" จะไม่คงที่ซึ่งฉันเชื่อว่าเป็นการปรับปรุง หากคุณต้องการให้เป็นแบบคงที่คุณต้องทำตามที่ JaredC แนะนำด้วยเทมเพลต


คุณกำลังแสดงเพียงด้านเดียวของมัน แสดงวิธีการยิงเหตุการณ์
Christopher Pisz

3

ตัวอย่างการทำงานที่สมบูรณ์จากโค้ดด้านบน .... สำหรับ 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 ได้หรือไม่? จำเป็นต้องใส่รหัสทั้งหมดของ OP ด้วยหรือไม่? OP ต้องการให้รหัสของเขาทำงานร่วมกับYourClass. OtherClassคุณดูเหมือนจะได้ลบระดับที่และเพิ่มความแตกต่างกัน นอกจากนี้คำถามยังได้รับคำตอบที่ดีอยู่แล้ว วิธีแก้ปัญหาของคุณดีกว่าแค่ไหนเพื่อที่จะโพสต์ข้อความ?
บีบแตร

ฉันไม่ได้บอกว่าการโพสต์ของฉันเป็นทางออกที่ดีกว่า ฉันแสดงวิธีใช้ "OtherClass" ในรูปแบบเทมเพลต
mohDady
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.