C++使用標(biāo)準(zhǔn)庫實(shí)現(xiàn)事件和委托以及信號(hào)和槽機(jī)制
在日常的程序開發(fā)中我們經(jīng)常會(huì)遇到以下的實(shí)際問題:
- 比如在一個(gè)文件下載完成時(shí),發(fā)送郵件或者微信通知告知用戶;
- 比如點(diǎn)擊一個(gè)按鈕時(shí),執(zhí)行相應(yīng)的業(yè)務(wù)邏輯;
- 比如當(dāng)用戶的金額少于一個(gè)閾值時(shí),通知用戶及時(shí)充值;
等等。
這些業(yè)務(wù)需求其實(shí)都對(duì)應(yīng)著觀察者模式,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變或者達(dá)到某種條件,所有的觀察者對(duì)象都會(huì)得到通知,觀察者模式通過面向?qū)ο笤O(shè)計(jì),實(shí)現(xiàn)軟件結(jié)構(gòu)的松耦合設(shè)計(jì)。
C#中的委托和事件以及Qt的信號(hào)和槽機(jī)制都是遵循了此種設(shè)計(jì)模式。在使用C#和Qt的過程中常常感嘆為什么C++標(biāo)準(zhǔn)庫不自帶這種快速開發(fā)的原生類呢(雖然boost中有),那么本文我們就使用C++模板實(shí)現(xiàn)一個(gè)簡(jiǎn)單但是夠用的C++事件工具類。
1 .Net的委托和事件
我們首先看下C#中的委托示例
class Program { //1、聲明委托類型 public delegate void AddDelegate(int a, int b); //2、委托函數(shù)(方法),參數(shù)需要和委托參數(shù)一致 public static void Add(int a, int b) { Console.WriteLine(a + b); } static void Main(string[] args) { //3、創(chuàng)建委托實(shí)例,將方法名Add作為參數(shù)綁定到該委托實(shí)例,也可以不使用new,直接AddDelegate addDelegate = Add; AddDelegate addDelegate = new AddDelegate(Add); //4、調(diào)用委托實(shí)例 addDelegate(1, 2); Console.ReadKey(); } }
從上述代碼可以看出C#的委托是不是與C++的函數(shù)指針聲明很像,先聲明一種表明返回值和形參的函數(shù)形式,然后把一個(gè)符合這種形式的函數(shù)當(dāng)做參數(shù)進(jìn)行傳遞,并最后進(jìn)行調(diào)用,類似于C的函數(shù)指針聲明以及C++的std::function。
看完委托之后,我們來看一個(gè)事件的示例,
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); } }
在上述代碼中我們聲明一個(gè)OnInsufficientBalance事件,這個(gè)事件在用戶賬戶低于100的時(shí)候觸發(fā),觸發(fā)函數(shù)使用郵件告知用戶。
2.Qt的信號(hào)和槽
Qt的信號(hào)和槽機(jī)制是由Qt實(shí)現(xiàn)的觀察者機(jī)制,可以通過信號(hào)觸發(fā)綁定的槽方法。
信號(hào)(Signal)就是在特定情況下被發(fā)射的事件,例如 PushButton 最常見的信號(hào)就是鼠標(biāo)單擊時(shí)發(fā)射的 clicked() 信號(hào)。
槽(Slot)就是對(duì)信號(hào)響應(yīng)的函數(shù)。槽函數(shù)可以與一個(gè)信號(hào)關(guān)聯(lián),當(dāng)信號(hào)被發(fā)射時(shí),關(guān)聯(lián)的槽函數(shù)被自動(dòng)執(zhí)行。
當(dāng)點(diǎn)擊一個(gè)按鈕時(shí),Qt發(fā)出按鈕被點(diǎn)擊的信號(hào),然后觸發(fā)信號(hào)綁定的開發(fā)者的自定義槽方法。
Qt的信號(hào)和槽方法與.Net的委托和事件大致相同,其中信號(hào)對(duì)應(yīng)事件,槽函數(shù)對(duì)應(yīng)委托。
示例代碼如下:
button1 = new QPushButton("close",this);//創(chuàng)建按鈕,指定父對(duì)象 button2 = new QPushButton("print",this);//創(chuàng)建按鈕,指定父對(duì)象 connect(button1,&QPushButton::clicked,this,&QWidget::close); connect(button2,&QPushButton::clicked,this,[](){ qDebug() << "關(guān)閉成功";//打印關(guān)閉成功 });
3.Duilib中委托和事件
在Duilib也有對(duì)委托和事件的簡(jiǎn)單實(shí)現(xiàn),我們可以在UIDelegate.h和UIDelegate.cpp中看到相應(yīng)的實(shí)現(xiàn)。
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實(shí)現(xiàn)委托與事件機(jī)制的源碼,我們可以看出整個(gè)的實(shí)現(xiàn)思路,通過CEventSource創(chuàng)建事件,通過MakeDelegate函數(shù)構(gòu)建綁定到事件上的委托函數(shù)CDelegate<O, T>,而這種委托函數(shù)的形式只能是void(void*)的形式。然后通過CEventSource重載操作符+=和-=添加和刪除委托函數(shù)。Duilib這種方式應(yīng)該就是最簡(jiǎn)單的事件和委托的原型,但是缺點(diǎn)是事件只能綁定固定形式的委托函數(shù)。
4.使用C++標(biāo)準(zhǔn)庫簡(jiǎn)單實(shí)現(xiàn)事件觸發(fā)機(jī)制
第3節(jié)Duilib的委托和事件不能自定義事件所綁定委托函數(shù)的形式,在本節(jié)中我們使用C++標(biāo)準(zhǔn)庫對(duì)事件機(jī)制進(jìn)行實(shí)現(xiàn),可以自定義事件綁定函數(shù)的形式。
具體的代碼如下:
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>對(duì)事件類Event進(jìn)行了模板化,使用變參模板typename ...Args自定義事件綁定的委托函數(shù)參數(shù)列表,可以接受多個(gè)不同類型的參數(shù)。使用std::vector存儲(chǔ)綁定事件的std::function<ReturnType(Args...)>的委托函數(shù),并重載+=操作符添加委托函數(shù)。
上述事件工具類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; // 靜態(tài)函數(shù)做委托函數(shù) Example example; button.OnClick += std::bind(&Example::Click, example); // 成員函數(shù)做委托函數(shù) button.OnClick += []() { std::cout << "Lambda Click" << std::endl; }; // 匿名函數(shù)做委托函數(shù) button.OnClick(); return 0; }
執(zhí)行結(jié)果:
Button Click
Example Click
Lambda Click
由于std::function的超強(qiáng)特性,我們可以為事件綁定靜態(tài)函數(shù)、類成員函數(shù)以及匿名函數(shù)。
5.總結(jié)
在本文中,我們對(duì).Net的事件和委托,Qt的信號(hào)和槽進(jìn)行了簡(jiǎn)單的介紹,然后通過引入Duilib中對(duì)于事件和委托的簡(jiǎn)單實(shí)現(xiàn),進(jìn)而擴(kuò)展了自定義的簡(jiǎn)單事件類Event,此類實(shí)現(xiàn)的比較簡(jiǎn)單,但是包含了事件實(shí)踐的核心思想,自己對(duì)于模板類,以及變參模板的使用又有了新的體會(huì)。
到此這篇關(guān)于C++使用標(biāo)準(zhǔn)庫實(shí)現(xiàn)事件和委托以及信號(hào)和槽機(jī)制的文章就介紹到這了,更多相關(guān)C++標(biāo)準(zhǔn)庫內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++實(shí)現(xiàn)獲取當(dāng)前時(shí)間(精確至秒,毫秒和微妙)
這篇文章主要為大家詳細(xì)介紹了c++實(shí)現(xiàn)獲取當(dāng)前時(shí)間(可以精確至秒,毫秒和微妙)的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下2023-11-11C語言 structural body結(jié)構(gòu)體詳解用法
C 數(shù)組允許定義可存儲(chǔ)相同類型數(shù)據(jù)項(xiàng)的變量,結(jié)構(gòu)是 C 編程中另一種用戶自定義的可用的數(shù)據(jù)類型,它允許您存儲(chǔ)不同類型的數(shù)據(jù)項(xiàng),結(jié)構(gòu)用于表示一條記錄,假設(shè)您想要跟蹤圖書館中書本的動(dòng)態(tài),您可能需要跟蹤每本書的下列屬性2021-10-10VS2019開發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫并進(jìn)行調(diào)用的實(shí)現(xiàn)
這篇文章主要介紹了VS2019開發(fā)簡(jiǎn)單的C/C++動(dòng)態(tài)鏈接庫并進(jìn)行調(diào)用的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03

C語言實(shí)現(xiàn)循環(huán)打印星號(hào)圖形再鏤空