详解c++实现信号槽
作者:newcly2003
用c++实现信号槽机制(signal-slot)
信号槽机制的个人理解:信号槽是在两个c++类对象之间建立联系的通道,其中一个对象可称之为信号发送者(sender),另一个对象可称之为信号接收者(recver),sender通过信号槽发出信号后,recver就可以执行函数进行某些操作。也就是说应用程序通过信号槽可以在两个互不相关的对象之间建立起逻辑关系,使程序开发变得简洁、方便。
信号槽本质是由c++定义的类组成,分为两个部分:槽类和信号类
槽类(slot):
可理解为插槽,其内部有两个私有成员,存储待执行的类对象和对象中的方法指针,可理解将信号接收者(recver)插入了插槽中。槽的另一端连接信号,通过在信号(signal)类的实例中存储槽(slot)指针的方式实现,若在signal对象中插入多个slot,则代表一个信号与多个recver建立了联系,当信号来临时,可以根据slot的插入先后顺序轮流执行事件方法。
信号类(signal):
其内部有一个容器,存储连接到信号类实例的槽类指针,同时提供一个执行方法,当方法调用时,在方法内部调用所存储的槽类指针所指向的槽内部保存的recver的方法。
若要使用信号槽,则信号发送者(sender)类需要在内部包含信号类(signal)实例,同时包含一个方法来调用signal上的执行方法。该方法称之为发出信号。
综上,作为sender的任意object类包含有signal成员,通过func发出信号,func内部调用object.signal上的执行方法,此处通过重载()实现,执行方法内部是循环调用保存在signal中的slot指针列表,而slot指针指向的slot实例中存储有recver和recver.func,通过slot的exec方法则可以执行func,这就实现了一个触发信号的完整流程。
为了实现任意sender和任意recver关联,slot和signal类并不知道要关联对象以及执行方法的具体类型和参数类型,因此要使用泛型编程
代码及说明如下(整理借鉴自csdn):
/// <summary> /// 先实现槽基类,包含两个虚函数对象,主要为后续调用提供接口 /// 子类负责实现exec方法 /// </summary> /// <typeparam name="TParam">Recver中待执行函数的参数类型</typeparam> template <class TParam> class SlotBase { public: virtual void Exec(TParam param) = 0; virtual ~SlotBase() {}; }; /// <summary> /// Slot子类,负责实现exec方法,通过exec调用recver.func,同时Slot构造函数负责初始化两个内部变量,将需要关联的recver和recver.func插入槽 /// </summary> /// <typeparam name="TRecver">recver类的具体类型</typeparam> /// <typeparam name="TParam">recver.func的参数类型</typeparam> template <class TRecver,class TParam> class Slot:public SlotBase<TParam> { public: Slot(TRecver* pObj, void (TRecver::* func)(TParam)) { m_pRecver = pObj; m_func = func; }; VOID Exec(TParam param) { (m_pRecver->*m_func)(param); }; private: TRecver* m_pRecver = NULL; void (TRecver::* m_func)(TParam); }; /// <summary> /// 信号类 /// 私有成员m_pSlotSet,存储槽指针的vector /// 重载(),在括号调用参数时,循环调用m_pSlotSet中存储的槽指针,将参数传递给槽的exec方法 /// bind():将一个槽与信号类实例关联起来 /// /// </summary> /// <typeparam name="TParam">待执行方法的参数类型</typeparam> /// <typeparam name="TRecver">recver类的类型</typeparam> template<class TParam> class Signal { public: template<class TRecver> void Bind(TRecver* pObj, void (TRecver::* func)(TParam)) { m_pSlotSet.push_back(new Slot<TRecver,TParam>(pObj, func)); }; void operator()(TParam param) { for(int i=0;i<m_pSlotSet.size();i++) { m_pSlotSet[i]->Exec(param); } }; ~Signal() { for (int i = 0; i < m_pSlotSet.size(); i++) delete m_pSlotSet[i]; }; private: std::vector<SlotBase<TParam>*> m_pSlotSet; }; //开始模拟 class RecverOne { public: void functionOne(int param) { std::cout << "这是接收者1中的某个方法执行:" << param << std::endl; }; }; class RecverTwo { public: void functionTwo(int param) { std::cout << "这是接收者2中的某个方法执行:" << param << std::endl; }; }; class SenderObj { public: //模拟值改变发出信号 void testSginal(int param) { valueChanged(param); }; public: Signal<int> valueChanged;//定义一个当值改变时触发的信号 }; //为了更方便地将sender中的signal与槽和recver关联,可以定义一个宏 #define connect(sender,signal,recver,method) ((sender)->signal.Bind(recver,method)) int main() { //开始测试 //先定义两个接收者 RecverOne* R1 = new RecverOne; RecverTwo* R2 = new RecverTwo; //定义一个发送者 SenderObj* sd = new SenderObj; //将R1和R2中的函数插入sd中的信号槽 connect(sd, valueChanged, R1, &RecverOne::functionOne); connect(sd, valueChanged, R2, &RecverTwo::functionTwo); //发送信号 sd->valueChanged(5); std::cout << "Hello World!\n"; }
总结
本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!