C++中实现回调的几种方式

目录

一、使用函数指针

二、新建类继承接口类并实现虚方法

三、直接继承接口类并实现虚方法

四、使用std::future和std::async


考虑两个类之间的异步场景:客户端调用类A处理某个任务,A需要委托B进行一些处理并返回结果;并且都是异步进行,即函数迅速返回之后在后台进行计算,完毕后通知调用者。

一、使用函数指针

类B需要有SetCallBack接口来接收函数指针,此处我们用std::function来实现。

B在进行ProcessAsync任务时另起一个线程并去进行较长时间的后台计算(此处用std::this_thread::sleep_for来模拟长时间计算),计算完成后通过刚才设置进来的函数指针来通知调用者。

#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");
        m_th = std::thread(&B::ProcessReal, this);
    }
    ~B() {
        printf("begin to destroy B\n");
        if (m_th.joinable()) {
            m_th.join();  //析构时保证子线程join进来
        }
        printf("~B\n");
    }
    
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;
    std::thread m_th;
};

类A在向类B设置回调时既可以用lambda表达式也可以用std::bind,建议用lambda。 A收到回调时打印计算结果result。

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

class A
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_unique<B>();
        m_b->SetCallBack([this](double res) {return OnProcessDone(res);}); //用lambda
        //m_b->SetCallBack(std::bind(&A::OnProcessDone, this, std::placeholders::_1)); //用bind
        m_b->ProcessAsync();
    }
    ~A() {
        printf("~A\n");
    }
private:
    void OnProcessDone(double result) {
        std::unique_lock<std::mutex> lock(m_mtx);
        printf("OnProcessDone, result = %f\n", result);
        m_res = result;
    }
    std::unique_ptr<B> m_b{nullptr};
    double m_res;
    std::mutex m_mtx;
};

主函数就不多说了

#include "A.h"
 
int main() {
    A a;
    a.DoTask();
    return 0;
}

这种方式有个问题是,B在回调A的成员函数时,可能A的实例已经被销毁,导致访问的成员变量已经不存在。解决方法是由A来管理B的生命周期,且在B析构时需要保证所有子线程都已经join进来。

 

二、新建类继承接口类并实现虚方法

B提供一个接口类Listener,接口类中有一个纯虚方法OnProcessDone;

#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");
        m_th = std::thread(&B::ProcessReal, this);
    }
    ~B() {
        if (m_th.joinable()) {
            m_th.join();
        }
        printf("~B\n");
    }
    
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;
    std::thread m_th;
};

而在A中可以新声明一个内部类MyListener来继承B::Listener,并实现这个虚方法。

A::MyListener声明成内部类的好处是:内部类默认是外部类的友元,则A::MyListener就可以访问类A的私有成员,若回调需要访问私有成员时这种写法十分方便。

A中的MyListener需要存储A的指针用于访问A的成员,可以用weak_ptr或裸指针,但不要用shared_ptr,否则会出现循环引用、导致内存泄漏。(A保存了B的sharedptr,B保存了MyListener的sharedptr,MyListener又保存了A的sharedptr,谁也不会被释放)。

本例使用了weak_ptr,首先检查它是否过期(expired),然后再进行对应的操作,避免操作指向A的野指针。

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

class A : public std::enable_shared_from_this<A>
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_shared<B>();
        m_b->SetCallBack(std::make_shared<MyListener>(weak_from_this()));
        m_b->ProcessAsync();
    }
    ~A() {
        printf("~A\n");
    }
private:
    class MyListener : public B::Listener {
    public:
        MyListener(std::weak_ptr<A> a) : m_a(a) {}
        void OnProcessDone(double result) override {
            printf("OnProcessDone, result = %f\n", result);
            if (!m_a.expired()) {
                std::shared_ptr<A> a = m_a.lock();
                std::unique_lock<std::mutex> lock(a->m_mtx);
                a->m_res = result; //内部类可以访问外部类的私有成员
            }
        }
    private:
        std::weak_ptr<A> m_a; //弱引用
    };
    
    std::shared_ptr<B> m_b{nullptr};
    double m_res;
    std::mutex m_mtx;
};

由于使用了std::enable_shared_from_this,所以主函数在生成A时也要改成智能指针:

#include "A.h"

int main() {
    std::shared_ptr<A> a = std::make_shared<A>();
    a->DoTask();
    return 0;
}

三、直接继承接口类并实现虚方法

由于我们的类A对类B是强引用,为了避免把A设置给B产生循环引用,需要把B对listener的引用设置为弱引用:

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

class B
{
public:
    class Listener {
    public:
        virtual void OnProcessDone(double result) = 0;
    };
    void SetCallBack(const std::weak_ptr<Listener> &cb) {
        m_callback = cb;
    }
    void ProcessAsync() {
        printf("class B, create thread to compute and return immediately\n");
        m_th = std::thread(&B::ProcessReal, this);
    }
    ~B() {
        if (m_th.joinable()) {
            m_th.join();
        }
        printf("~B\n");
    }
    
private:
    void ProcessReal() {
        printf("class B, Process for a long time...\n");
        std::this_thread::sleep_for(std::chrono::seconds(3));
        if (m_callback.expired()) {
            printf("callback expired\n");
            return;
        }
        printf("callback\n");
        m_callback.lock()->OnProcessDone(3.72);
    }
    std::weak_ptr<Listener> m_callback; //弱引用
    std::thread m_th;
};

 

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

class A : public B::Listener, public std::enable_shared_from_this<A>
{
public:
    void DoTask() {
        printf("class A, DoTask\n");
        m_b = std::make_unique<B>();
        m_b->SetCallBack(weak_from_this());
        m_b->ProcessAsync();
    }
    ~A() {
        printf("~A\n");
    }
private:
    void OnProcessDone(double result) override {
        printf("OnProcessDone, result = %f\n", result);
        std::unique_lock<std::mutex> lock(m_mtx);
        m_res = result;
    }
    std::unique_ptr<B> m_b{nullptr};
    double m_res;
    std::mutex m_mtx;
};

 方法二和方法三的区别是判断引用失效的位置不同,一个是在新建的类中,一个是在被委托的类中。

 

四、使用std::future和std::async

这种方式已经没有回调了,做法是在类A中启动一个std::async,将B的同步处理函数变成异步的,这样就不需要向B设置回调了,B的实现会非常简单。 A中在需要计算结果时调用future的.get()就可以取到结果。

#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;
    }
};

 要注意std::future是不可拷贝的,所以把future传到另一个线程时要move过去。

#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;
};

 

想深入进阶了解的可以看这篇,chromium实现的高效回调  https://cloud.tencent.com/developer/article/1519851

  • 23
    点赞
  • 86
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值