C++使用标准库实现事件和委托以及信号和槽机制
作者:HW140701
在日常的程序开发中我们经常会遇到以下的实际问题:
- 比如在一个文件下载完成时,发送邮件或者微信通知告知用户;
- 比如点击一个按钮时,执行相应的业务逻辑;
- 比如当用户的金额少于一个阈值时,通知用户及时充值;
等等。
这些业务需求其实都对应着观察者模式,当一个对象的状态发生改变或者达到某种条件,所有的观察者对象都会得到通知,观察者模式通过面向对象设计,实现软件结构的松耦合设计。
C#中的委托和事件以及Qt的信号和槽机制都是遵循了此种设计模式。在使用C#和Qt的过程中常常感叹为什么C++标准库不自带这种快速开发的原生类呢(虽然boost中有),那么本文我们就使用C++模板实现一个简单但是够用的C++事件工具类。
1 .Net的委托和事件
我们首先看下C#中的委托示例
class Program { //1、声明委托类型 public delegate void AddDelegate(int a, int b); //2、委托函数(方法),参数需要和委托参数一致 public static void Add(int a, int b) { Console.WriteLine(a + b); } static void Main(string[] args) { //3、创建委托实例,将方法名Add作为参数绑定到该委托实例,也可以不使用new,直接AddDelegate addDelegate = Add; AddDelegate addDelegate = new AddDelegate(Add); //4、调用委托实例 addDelegate(1, 2); Console.ReadKey(); } }
从上述代码可以看出C#的委托是不是与C++的函数指针声明很像,先声明一种表明返回值和形参的函数形式,然后把一个符合这种形式的函数当做参数进行传递,并最后进行调用,类似于C的函数指针声明以及C++的std::function。
看完委托之后,我们来看一个事件的示例,
public class Account { private float bank_savings = 1000; // 存款金额 public event Action OnInsufficientBalance; // 余额不足事件 public void cosume(float money) { bank_savings -= money; if (bank_savings < 100) { OnInsufficientBalance.InVoke(); } } } public class Notify { public static void Email() { Console.WriteLine("Insufficient Balance"); } } class Program { static void Main(string[] args) { var account = new Account(); account.OnInsufficientBalance += Notify.Email; account.cosume(1000); } }
在上述代码中我们声明一个OnInsufficientBalance事件,这个事件在用户账户低于100的时候触发,触发函数使用邮件告知用户。
2.Qt的信号和槽
Qt的信号和槽机制是由Qt实现的观察者机制,可以通过信号触发绑定的槽方法。
信号(Signal)就是在特定情况下被发射的事件,例如 PushButton 最常见的信号就是鼠标单击时发射的 clicked() 信号。
槽(Slot)就是对信号响应的函数。槽函数可以与一个信号关联,当信号被发射时,关联的槽函数被自动执行。
当点击一个按钮时,Qt发出按钮被点击的信号,然后触发信号绑定的开发者的自定义槽方法。
Qt的信号和槽方法与.Net的委托和事件大致相同,其中信号对应事件,槽函数对应委托。
示例代码如下:
button1 = new QPushButton("close",this);//创建按钮,指定父对象 button2 = new QPushButton("print",this);//创建按钮,指定父对象 connect(button1,&QPushButton::clicked,this,&QWidget::close); connect(button2,&QPushButton::clicked,this,[](){ qDebug() << "关闭成功";//打印关闭成功 });
3.Duilib中委托和事件
在Duilib也有对委托和事件的简单实现,我们可以在UIDelegate.h和UIDelegate.cpp中看到相应的实现。
UIDelegate.h
#ifndef __UIDELEGATE_H__ #define __UIDELEGATE_H__ #pragma once namespace DuiLib { class DUILIB_API CDelegateBase { public: CDelegateBase(void* pObject, void* pFn); CDelegateBase(const CDelegateBase& rhs); virtual ~CDelegateBase(); bool Equals(const CDelegateBase& rhs) const; bool operator() (void* param); virtual CDelegateBase* Copy() const = 0; // add const for gcc protected: void* GetFn(); void* GetObject(); virtual bool Invoke(void* param) = 0; private: void* m_pObject; void* m_pFn; }; class CDelegateStatic: public CDelegateBase { typedef bool (*Fn)(void*); public: CDelegateStatic(Fn pFn) : CDelegateBase(NULL, pFn) { } CDelegateStatic(const CDelegateStatic& rhs) : CDelegateBase(rhs) { } virtual CDelegateBase* Copy() const { return new CDelegateStatic(*this); } protected: virtual bool Invoke(void* param) { Fn pFn = (Fn)GetFn(); return (*pFn)(param); } }; template <class O, class T> class CDelegate : public CDelegateBase { typedef bool (T::* Fn)(void*); public: CDelegate(O* pObj, Fn pFn) : CDelegateBase(pObj, *(void**)&pFn) { } CDelegate(const CDelegate& rhs) : CDelegateBase(rhs) { } virtual CDelegateBase* Copy() const { return new CDelegate(*this); } protected: virtual bool Invoke(void* param) { O* pObject = (O*) GetObject(); union { void* ptr; Fn fn; } func = { GetFn() }; return (pObject->*func.fn)(param); } private: Fn m_pFn; }; template <class O, class T> CDelegate<O, T> MakeDelegate(O* pObject, bool (T::* pFn)(void*)) { return CDelegate<O, T>(pObject, pFn); } inline CDelegateStatic MakeDelegate(bool (*pFn)(void*)) { return CDelegateStatic(pFn); } class DUILIB_API CEventSource { typedef bool (*FnType)(void*); public: ~CEventSource(); operator bool(); void operator+= (const CDelegateBase& d); // add const for gcc void operator+= (FnType pFn); void operator-= (const CDelegateBase& d); void operator-= (FnType pFn); bool operator() (void* param); protected: CDuiPtrArray m_aDelegates; }; } // namespace DuiLib #endif // __UIDELEGATE_H__
UIDelegate.cpp
#include "StdAfx.h" namespace DuiLib { CDelegateBase::CDelegateBase(void* pObject, void* pFn) { m_pObject = pObject; m_pFn = pFn; } CDelegateBase::CDelegateBase(const CDelegateBase& rhs) { m_pObject = rhs.m_pObject; m_pFn = rhs.m_pFn; } CDelegateBase::~CDelegateBase() { } bool CDelegateBase::Equals(const CDelegateBase& rhs) const { return m_pObject == rhs.m_pObject && m_pFn == rhs.m_pFn; } bool CDelegateBase::operator() (void* param) { return Invoke(param); } void* CDelegateBase::GetFn() { return m_pFn; } void* CDelegateBase::GetObject() { return m_pObject; } CEventSource::~CEventSource() { for( int i = 0; i < m_aDelegates.GetSize(); i++ ) { CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]); if( pObject) delete pObject; } } CEventSource::operator bool() { return m_aDelegates.GetSize() > 0; } void CEventSource::operator+= (const CDelegateBase& d) { for( int i = 0; i < m_aDelegates.GetSize(); i++ ) { CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]); if( pObject && pObject->Equals(d) ) return; } m_aDelegates.Add(d.Copy()); } void CEventSource::operator+= (FnType pFn) { (*this) += MakeDelegate(pFn); } void CEventSource::operator-= (const CDelegateBase& d) { for( int i = 0; i < m_aDelegates.GetSize(); i++ ) { CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]); if( pObject && pObject->Equals(d) ) { delete pObject; m_aDelegates.Remove(i); return; } } } void CEventSource::operator-= (FnType pFn) { (*this) -= MakeDelegate(pFn); } bool CEventSource::operator() (void* param) { for( int i = 0; i < m_aDelegates.GetSize(); i++ ) { CDelegateBase* pObject = static_cast<CDelegateBase*>(m_aDelegates[i]); if( pObject && !(*pObject)(param) ) return false; } return true; } } // namespace DuiLib
从上述Duilib实现委托与事件机制的源码,我们可以看出整个的实现思路,通过CEventSource创建事件,通过MakeDelegate函数构建绑定到事件上的委托函数CDelegate<O, T>,而这种委托函数的形式只能是void(void*)的形式。然后通过CEventSource重载操作符+=和-=添加和删除委托函数。Duilib这种方式应该就是最简单的事件和委托的原型,但是缺点是事件只能绑定固定形式的委托函数。
4.使用C++标准库简单实现事件触发机制
第3节Duilib的委托和事件不能自定义事件所绑定委托函数的形式,在本节中我们使用C++标准库对事件机制进行实现,可以自定义事件绑定函数的形式。
具体的代码如下:
Event.hpp
#ifndef _EVENT_H_ #define _EVENT_H_ #include <vector> #include <functional> #include <type_traits> #include <memory> #include <assert.h> namespace stubbornhuang { // 原型 template<typename Prototype> class Event; // 特例 template<typename ReturnType, typename ...Args> class Event <ReturnType(Args...)> { private: using return_type = ReturnType; using function_type = ReturnType(Args...); using stl_function_type = std::function<function_type>; using pointer = ReturnType(*)(Args...); private: class EventHandler { public: EventHandler(stl_function_type func) { assert(func != nullptr); m_Handler = func; } void Invoke(Args ...args) { if (m_Handler != nullptr) { m_Handler(args...); } } private: stl_function_type m_Handler; }; public: void operator += (stl_function_type func) { std::shared_ptr<EventHandler> pEventHandler = std::make_shared<EventHandler>(func); if (pEventHandler != nullptr) { m_HandlerVector.push_back(std::move(pEventHandler)); } } void Connect(stl_function_type func) { std::shared_ptr<EventHandler> pEventHandler = std::make_shared<EventHandler>(func); if (pEventHandler != nullptr) { m_HandlerVector.push_back(std::move(pEventHandler)); } } void operator() (Args ...args) { for (int i = 0; i < m_HandlerVector.size(); ++i) { if (m_HandlerVector[i] != nullptr) { m_HandlerVector[i]->Invoke(args...); } } } void Trigger(Args ...args) { for (int i = 0; i < m_HandlerVector.size(); ++i) { if (m_HandlerVector[i] != nullptr) { m_HandlerVector[i]->Invoke(args...); } } } private: std::vector<std::shared_ptr<EventHandler>> m_HandlerVector; }; } #endif // !_EVENT_H_
在上述代码中我们使用template<typename ReturnType, typename ...Args>对事件类Event进行了模板化,使用变参模板typename ...Args自定义事件绑定的委托函数参数列表,可以接受多个不同类型的参数。使用std::vector存储绑定事件的std::function<ReturnType(Args...)>的委托函数,并重载+=操作符添加委托函数。
上述事件工具类Event的使用示例如下:
#include <iostream> #include "Event.h" class Button { public: Button() { } virtual~Button() { } public: stubbornhuang::Event<void()> OnClick; }; void Click() { std::cout << "Button Click" << std::endl; } class Example { public: void Click() { std::cout << "Example Click" << std::endl; } }; int main() { Button button; button.OnClick += Click; // 静态函数做委托函数 Example example; button.OnClick += std::bind(&Example::Click, example); // 成员函数做委托函数 button.OnClick += []() { std::cout << "Lambda Click" << std::endl; }; // 匿名函数做委托函数 button.OnClick(); return 0; }
执行结果:
Button Click
Example Click
Lambda Click
由于std::function的超强特性,我们可以为事件绑定静态函数、类成员函数以及匿名函数。
5.总结
在本文中,我们对.Net的事件和委托,Qt的信号和槽进行了简单的介绍,然后通过引入Duilib中对于事件和委托的简单实现,进而扩展了自定义的简单事件类Event,此类实现的比较简单,但是包含了事件实践的核心思想,自己对于模板类,以及变参模板的使用又有了新的体会。
到此这篇关于C++使用标准库实现事件和委托以及信号和槽机制的文章就介绍到这了,更多相关C++标准库内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!