Gọi lại C ++ bằng cách sử dụng thành viên lớp


89

Tôi biết điều này đã được hỏi rất nhiều lần, và do đó, rất khó để tìm hiểu sơ bộ và tìm một ví dụ đơn giản về những gì hoạt động.

Tôi đã có cái này, nó đơn giản và nó hoạt động cho 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);
};

Làm thế nào mà có thể được viết lại như vậy EventHandler::addHandler()sẽ làm việc với cả hai MyClassYourClass. Tôi xin lỗi nhưng đó chỉ là cách bộ não của tôi hoạt động, tôi cần xem một ví dụ đơn giản về những gì hoạt động trước khi tôi có thể hiểu tại sao / cách nó hoạt động. Nếu bạn có một cách yêu thích để biến nó thành công việc này, bây giờ là lúc để thể hiện nó, vui lòng đánh dấu mã đó và đăng lại.

[biên tập]

Nó đã được trả lời nhưng câu trả lời đã bị xóa trước khi tôi có thể đánh dấu chọn. Câu trả lời trong trường hợp của tôi là một hàm mẫu. Đã thay đổi addHandler thành này ...

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
Ai đã đăng ví dụ về hàm mẫu? Bạn có dấu kiểm, nhưng bạn đã xóa câu trả lời của mình trong khi tôi đang kiểm tra. Nó đã làm chính xác những gì tôi cần. Một mẫu chức năng đơn giản đã bị mất trong đống tất cả các thông tin khác mà tôi đang đọc. Câu trả lời của bạn đã được thêm dưới dạng chỉnh sửa cho câu hỏi.
BentFX

Tôi nghĩ đó là JaredC. Bạn có thể cần phải săn anh ta xuống = P
WhozCraig

Câu trả lời:


182

Thay vì có các phương thức tĩnh và truyền xung quanh một con trỏ đến cá thể lớp, bạn có thể sử dụng chức năng trong tiêu chuẩn C ++ 11 mới: std::functionstd::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);
        }
};

Các addHandlerphương pháp hiện nay chấp nhận một std::functionđối số, và điều này "chức năng đối tượng" không có giá trị trả về và mất một số nguyên như là đối số.

Để liên kết nó với một chức năng cụ thể, bạn sử dụng 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;
}

Bạn cần sử dụng std::bindkhi thêm trình xử lý, vì bạn cần xác định rõ ràng thiscon trỏ ngầm định khác làm đối số. Nếu bạn có một chức năng rảnh, bạn không cần phải sử dụng std::bind:

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

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

Việc để trình xử lý sự kiện sử dụng std::functioncác đối tượng, cũng giúp bạn có thể sử dụng các hàm lambda mới của C ++ 11 :

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

4
Cảm ơn Joachim! Ví dụ này thực hiện rất nhiều để phân minh hàm std :: và std :: bind. Tôi chắc chắn sẽ sử dụng nó trong tương lai! chỉnh sửa tôi vẫn không nhận được lambda ở tất cả :)
BentFX

3
Tôi đã gấp nó vào dự án lớn hơn của mình (khoảng 6.000 dòng. Đó là điều lớn đối với tôi.) Nó sử dụng các vectơ định nghĩa nút với các lệnh gọi lại và tham số khác nhau, sau đó cung cấp thông tin đó cho wxWidgets, vì vậy các đối tượng có thể quản lý các nút của riêng chúng trong wxFrame. Điều này đã đơn giản hóa rất nhiều thứ! Tôi không thể nói đủ, internet chứa quá nhiều kỹ thuật và quan điểm, và không đủ ví dụ đơn giản.
BentFX

1
@ user819640 Không có "unbind", thay vào đó std::bindchỉ trả về một đối tượng (không xác định) và khi bạn hoàn thành nó, bạn có thể để nó ra khỏi phạm vi. Nếu đối tượng bị ràng buộc bị hủy và bạn cố gắng gọi hàm, bạn sẽ nhận được hành vi không xác định .
Một số lập trình viên dude

2
handler->addHandler(), có nghĩa là bạn tạo một đối tượng ở EventHandlerđâu đó? Câu trả lời tốt btw, +1.
gsamaras

1
Lưu ý rằng bạn cần số lượng trình giữ chỗ phù hợp với số lượng đối số, vì vậy nếu có hai đối số trong lệnh gọi lại, bạn cần sử dụng ...., _1, _2), v.v.
Den-Jason

5

Đây là một phiên bản ngắn gọn hoạt động với các lệnh gọi lại phương thức lớp và với các lệnh gọi lại hàm thông thường. Trong ví dụ này, để hiển thị cách các tham số được xử lý, hàm gọi lại nhận hai tham số: 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);

Điều này hạn chế mã C ++ 11 cụ thể đối với phương thức addCallback và dữ liệu riêng tư trong Người gọi lớp. Đối với tôi, ít nhất, điều này giảm thiểu khả năng mắc lỗi khi thực hiện nó.


3

Những gì bạn muốn làm là tạo một giao diện xử lý mã này và tất cả các lớp của bạn triển khai giao diện.

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
{

Lưu ý rằng để điều này hoạt động, chức năng "Gọi lại" không phải là tĩnh mà tôi tin rằng đó là một cải tiến. Nếu bạn muốn nó ở trạng thái tĩnh, bạn cần thực hiện như JaredC đề xuất với các mẫu.


Bạn chỉ đang hiển thị một mặt của nó. Hiển thị cách kích hoạt sự kiện.
Christopher Pisz

2

Một ví dụ làm việc hoàn chỉnh từ đoạn mã trên .... cho 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

Đầu ra phải là:

Compiler:201103

Handler added...
Result:6

1

MyClassYourClasscả hai đều có thể được dẫn xuất từ SomeonesClassđó có một Callbackphương thức trừu tượng (ảo) . Của bạn addHandlersẽ chấp nhận các đối tượng thuộc loại SomeonesClassMyClassYourClasscó thể ghi đè Callbackđể cung cấp việc triển khai hành vi gọi lại cụ thể của chúng.


Vì những gì tôi đang làm, tôi đã đùa giỡn với ý tưởng này. Nhưng vì số lượng các lớp khác nhau rộng rãi sẽ sử dụng trình xử lý của tôi, tôi không xem nó như một tùy chọn.
BentFX

0

Nếu bạn có các lệnh gọi lại với các tham số khác nhau, bạn có thể sử dụng các mẫu như sau:
// biên dịch với: 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
Bạn có thể vui lòng giải thích những gì bạn đã làm để giải quyết vấn đề của OP? Có thực sự cần thiết để bao gồm mã hoàn chỉnh của OP không? OP muốn mã của anh ấy hoạt động với mã của anh ấy YourClass. Dường như bạn đã xóa lớp đó và thêm một lớp khác OtherClass. Hơn nữa, câu hỏi đã có một câu trả lời được nhiều người đón nhận. Giải pháp của bạn tốt hơn đến đâu để nó đáng được đăng tải?
bấm còi

Tôi không nói Bài đăng của tôi là một giải pháp tốt hơn. Tôi đã chỉ ra cách sử dụng "OtherClass" theo cách mẫu.
mohDady
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.