網站首頁 編程語言 正文
在日常的程序開發中我們經常會遇到以下的實際問題:
- 比如在一個文件下載完成時,發送郵件或者微信通知告知用戶;
- 比如點擊一個按鈕時,執行相應的業務邏輯;
- 比如當用戶的金額少于一個閾值時,通知用戶及時充值;
等等。
這些業務需求其實都對應著觀察者模式,當一個對象的狀態發生改變或者達到某種條件,所有的觀察者對象都會得到通知,觀察者模式通過面向對象設計,實現軟件結構的松耦合設計。
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,此類實現的比較簡單,但是包含了事件實踐的核心思想,自己對于模板類,以及變參模板的使用又有了新的體會。
原文鏈接:https://blog.csdn.net/HW140701/article/details/127647414
相關推薦
- 2022-08-01 C++鏈式二叉樹深入分析_C 語言
- 2024-02-16 SpringBoot的默認組件掃描
- 2022-10-04 基于WPF實現帶蒙版的MessageBox消息提示框_C#教程
- 2022-07-04 Python如何查看兩個數據庫的同名表的字段名差異_python
- 2023-05-16 Golang的鎖機制使用及說明_Golang
- 2022-10-18 Qt?TCP實現簡單通信功能_C 語言
- 2022-06-07 ASP.NET?Core依賴關系注入_基礎應用
- 2022-11-27 Unity?數據存儲和讀取的方法匯總_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支