实现C++回调机制

本文介绍了如何使用C++模板实现发布订阅模式,通过signal/slot机制,实现回调功能。示例代码展示了如何创建slot类和signal类,以及如何在Publisher类中使用它们。在main函数中,展示了不同类型的Subscriber可以通过信号槽机制进行连接和通信,突破了原来实现的类型限制。
摘要由CSDN通过智能技术生成

boost中使用signal/slot实现了回调功能,也就是经典的发布订阅模式。我们这里采用模版来完成这个功能,设计类图如下:

c50c2f3043bcb6f5b98bbe29c82ff3ad.png

发布者Publisher管理信号msignal,对外暴露set接口,外部调用set时触发成员对象msignal动作。

msignal通过动态数组管理信号槽slot,提供connect与operator()两个接口,connect将监听者挂载到一个slot上,operator()会依次调用slot上订阅者的回调函数。

源码如下:

#include <vector>
#include <iostream>


template<typename T, typename T1>
class slot
{
public:
    slot(T* pObj,void (T::*pMemberFunc)(T1))
    {
        m_pObj=pObj;
        m_pMemberFunc=pMemberFunc;
    }
    void Execute(T1 para)
{
        (m_pObj->*m_pMemberFunc)(para);
    }
private:
    T* m_pObj;
    void (T::*m_pMemberFunc)(T1);
};


template<typename T, typename T1>
class msignal
{
public:
    void connect(T* pObj,void (T::*pMemberFunc)(T1 para))
{
        m_slots.push_back(new slot<T,T1>(pObj,pMemberFunc));
    }
    ~msignal()
    {
        for (auto ite=m_slots.begin();ite!=m_slots.end();ite++)
        {
            delete *ite;
        }
    }
    void operator()(T1 para)
{
        for (auto ite=m_slots.begin();ite!=m_slots.end();ite++)
        {
            (*ite)->Execute(para);
        }
    }
    
private:
    std::vector<slot<T,T1>* > m_slots;
};


class Subscriber
{
public:
    void callback1(int a)
{
        std::cout<<"cb1: "<<a<<std::endl;
    }
    void callback2(int a)
{
        std::cout<<"cb2: "<<a<<std::endl;
    }
};


template<typename T, typename T1>
class Publisher
{
public:
    Publisher(): m_value(0)  {}
    int get_value()
{
        return m_value;
    }
    void set_value(int new_value)
{
        if (new_value!=m_value)
        {
            m_value=new_value;
            m_sig(new_value);
        }
    }
    msignal<T,T1> m_sig;
private:
    int m_value;
};






int main(int argc,char** arg)
{
    Subscriber r1;
    Subscriber r2;
    Publisher<Subscriber,int> s;
    s.m_sig.connect(&r1,&Subscriber::callback1);
    s.m_sig.connect(&r1,&Subscriber::callback2);
    s.m_sig.connect(&r2,&Subscriber::callback1);
    s.set_value(1);
    return 0;
}

上一版实现有个局限性

上一版实现有个局限性,就是发布者Publisher在实例化的时候就需要知道Subscriber的类型,且只能通知单类型的通知,改进办法是将订阅者类型推导延迟到调用connect时,实现如下:

#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>


class A{
public:
    void Clicked(int id)
{
        std::cout << "A::Clicked" << std::endl;
    }
};
class B{
public:
    void Clicked(int id)
{
        std::cout << "B::Clicked" << std::endl;
    }
};
class SlotBase{
public:
    virtual void OnSignal(int id) = 0;
};
template<class T>
class Slot : public SlotBase{
public:
    typedef void (T::*MemberFuncType)(int);
    Slot(T* obj, MemberFuncType func)
        :obj_(obj), func_(func)
    {
    }
      
    virtual void OnSignal(int id)
{
        (obj_->*func_)(id);
    }


private:
    T* obj_;
    MemberFuncType func_;
};
template<class T>
SlotBase* MakeSlot(T * p, void (T::*func)(int))
{
    return new Slot<T>( p, func ) ;
}
class Signal
{
public:
    virtual ~Signal()
    {
        std::for_each(slots_.begin(), slots_.end(), SignalDelete());
    }
    struct SignalDelete: public std::unary_function<SlotBase*, void>
    {
    public:
        void operator()(SlotBase*& slot) const
{
            delete slot;
        }
    };
    struct SignalAction: public std::binary_function<SlotBase*, int, void>
    {
    public:
        void operator()(SlotBase*& slot, const int& i) const
{
            slot->OnSignal(i);
        }
    };
    void signal(int id)
{
        std::for_each(slots_.begin(), slots_.end(), bind2nd(SignalAction(), id));
    }
    void connect( SlotBase* s)
{
        slots_.push_back(s);
    }
protected:
    std::vector< SlotBase* > slots_;
};
int main()
{
    A a;
    B b;
    Signal signal;
    signal.connect(MakeSlot(&a, &A::Clicked));
    signal.connect(MakeSlot(&b, &B::Clicked));
    signal.signal(1);
    return 0;
}

可以看到,signal可以挂载不同类型的监听者。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值