Several elegant ways to implement callbacks in C++

table of Contents

One, use function pointers

Second, implement virtual methods

Three, use std::future and std::async


 

Consider the asynchronous scenario between two classes: the client calls class A to handle a certain task, and A needs to entrust B to perform some processing and return the result; and they are all asynchronous, that is, the function returns quickly and then the calculation is performed in the background, and the notification is completed. Caller.

One, use function pointers

Class B needs to have the SetCallBack interface to receive the function pointer, here we use std::function to achieve. B. When the ProcessAsync task is performed, another thread is started and detached to perform a longer background calculation (here std::this_thread::sleep_for is used to simulate a long calculation). After the calculation is completed, the function pointer that has just been set is used to Notify the caller.

#include <iostream>
#include <functional>
#include <thread>

class B
{
public:
    void SetCallBack(const std::function<void(double)> &cb) {
        m_callback = cb;
    }
    void ProcessAsync() {
        printf("class B, create thread to compute and return immediately\n");
        std::thread th(&B::ProcessReal, this);
        th.detach();
    }
    
private:
    void ProcessReal() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        if (m_callback) {
            printf("callback\n");
            m_callback(3.72);
        }
    }
    std::function<void(double)> m_callback;
};

Class A can use either lambda expressions or std::bind when setting callbacks to class B. This depends on personal preference. When A receives the callback, print the result of the calculation.

#include <iostream>
#include <memory>
#include <functional>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        //m_b->SetCallBack([this](double res) {return OnProcessDone(res);});
        m_b->SetCallBack(std::bind(&A::OnProcessDone, this, std::placeholders::_1));
        m_b->ProcessAsync();
    }
private:
    void OnProcessDone(double result) {
        printf("OnProcessDone, result = %f\n", result);
    }
    std::shared_ptr<B> m_b{nullptr};
};

Not much to say about the main function

#include <thread>
#include <chrono>
#include "A.h"

int main() {
    A a;
    a.DoTask();
    std::this_thread::sleep_for(std::chrono::seconds(5));
    return 0;
}

 

Second, implement virtual methods

B provides an interface class Listener, which has a pure virtual method OnProcessDone; and A can declare a new MyListener to inherit B::Listener and implement this virtual method.

Here, both A::MyListener and B::Listener are declared as internal classes. The advantage of this is that the internal class is a friend of the external class by default, so A::MyListener can access the private members of class A, in the callback This way of writing is very convenient when you need to access private members.

#include <iostream>
#include <functional>
#include <thread>
#include <memory>

class B
{
public:
    class Listener {
    public:
        virtual void OnProcessDone(double result) = 0;
    };
    void SetCallBack(const std::shared_ptr<Listener> &cb) {
        m_callback = cb;
    }
    void ProcessAsync() {
        printf("class B, create thread to compute and return immediately\n");
        std::thread th(&B::ProcessReal, this);
        th.detach();
    }
    
private:
    void ProcessReal() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        if (m_callback) {
            printf("callback\n");
            m_callback->OnProcessDone(3.72);
        }
    }
    std::shared_ptr<Listener> m_callback;
};
#include <iostream>
#include <memory>
#include <functional>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        m_b->SetCallBack(std::make_shared<MyListener>(this));
        m_b->ProcessAsync();
    }
private:
    class MyListener : public B::Listener {
    public:
        MyListener(A *a) : m_a(a) {}
        void OnProcessDone(double result) {
            printf("OnProcessDone, result = %f\n", result);
            m_a->m_res = result;
        }
    private:
        A *m_a;
    };
    
    std::shared_ptr<B> m_b{nullptr};
    double m_res;
};

 

Three, use std::future and std::async

Start a std::async in class A to make B's synchronous processing function asynchronous, so that there is no need to set a callback to B, and the implementation of B will be very simple. In A, you can get the result by calling future's .get() when you need to calculate the result.

#include <iostream>
#include <thread>

class B
{
public:
    double ProcessSync() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        return 3.72;
    }
};

 Note that std::future is not copyable, so move the future when passing it to another thread.

#include <iostream>
#include <memory>
#include <functional>
#include <future>
#include "B.h"

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        std::future<double> fut = std::async(std::bind(&B::ProcessSync, m_b));
        std::thread th(&A::WaitForProcessDone, this, std::move(fut));
        th.detach();
    }
private:
    void WaitForProcessDone(std::future<double> &&fut) {
        double result = fut.get();
        printf("ProcessDone, result = %f\n", result);
        m_res = result;
    }
    std::shared_ptr<B> m_b{nullptr};
    double m_res;
};

 

Guess you like

Origin blog.csdn.net/yuejisuo1948/article/details/113280498