• C++ - 使用标准库实现事件和委托,信号和槽机制


    在日常的程序开发中我们经常会遇到以下的实际问题:

    • 比如在一个文件下载完成时,发送邮件或者微信通知告知用户;
    • 比如点击一个按钮时,执行相应的业务逻辑;
    • 比如当用户的金额少于一个阈值时,通知用户及时充值;

    等等。

    这些业务需求其实都对应着观察者模式,当一个对象的状态发生改变或者达到某种条件,所有的观察者对象都会得到通知,观察者模式通过面向对象设计,实现软件结构的松耦合设计。

    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();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    从上述代码可以看出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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    在上述代码中我们声明一个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() << "关闭成功";//打印关闭成功
        });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3 Duilib中委托和事件

    在Duilib也有对委托和事件的简单实现,我们可以在UIDelegate.hUIDelegate.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__
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    从上述Duilib实现委托与事件机制的源码,我们可以看出整个的实现思路,通过CEventSource创建事件,通过MakeDelegate函数构建绑定到事件上的委托函数CDelegate,而这种委托函数的形式只能是void(void*)的形式。然后通过CEventSource重载操作符+=-=添加和删除委托函数。Duilib这种方式应该就是最简单的事件和委托的原型,但是缺点是事件只能绑定固定形式的委托函数。

    4 使用C++标准库简单实现事件触发机制

    第3节Duilib的委托和事件不能自定义事件所绑定委托函数的形式,在本节中我们使用C++标准库对事件机制进行实现,可以自定义事件绑定函数的形式。

    具体的代码如下:

    Event.hpp

    #ifndef _EVENT_H_
    #define _EVENT_H_
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    
    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_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    在上述代码中我们使用template对事件类Event进行了模板化,使用变参模板typename ...Args自定义事件绑定的委托函数参数列表,可以接受多个不同类型的参数。使用std::vector存储绑定事件的std::function的委托函数,并重载+=操作符添加委托函数。

    上述事件工具类Event的使用示例如下:

    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    执行结果:

    Button Click
    Example Click
    Lambda Click
    
    • 1
    • 2
    • 3

    由于std::function的超强特性,我们可以为事件绑定静态函数、类成员函数以及匿名函数。

    5 总结

    在本文中,我们对.Net的事件和委托,Qt的信号和槽进行了简单的介绍,然后通过引入Duilib中对于事件和委托的简单实现,进而扩展了自定义的简单事件类Event,此类实现的比较简单,但是包含了事件实践的核心思想,自己对于模板类,以及变参模板的使用又有了新的体会。

    6 整理修改之后的开源项目

    最近有时间,在第4小节的基础上,对代码进行了扩展和整理,并在Github上进行了开源,项目地址为:https://github.com/HW140701/TinyEvent,有兴趣的同学可以Star或者Fork。

    修改之后的Event事件类如下

    /*
    Author:StubbornHuang
    Data:2023.1.31
    Email:stubbornhuang@qq.com
    */
    
    
    #ifndef _EVENT_H_
    #define _EVENT_H_
    
    #include 
    #include 
    #include 
    
    #ifndef EVENT_NO_THREAD_SAFETY
    #define EVENT_THREAD_SAFETY
    #endif // !EVENT_NO_THREAD_SAFETY
    
    #ifdef EVENT_THREAD_SAFETY
    #include 
    #include 
    #endif // EVENT_THREAD_SAFETY
    
    #ifdef EVENT_THREAD_SAFETY
    #define DELEGATE_ID_TYPE std::atomic_uint64_t
    #else
    #define DELEGATE_ID_TYPE std::uint64_t
    #endif // EVENT_THREAD_SAFETY
    
    namespace stubbornhuang
    {
    	static DELEGATE_ID_TYPE DELEGATE_ID = 1;
    
    	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 std_function_type = std::function<function_type>;
    		using function_pointer = ReturnType(*)(Args...);
    
    	private:
    		class Delegate
    		{
    		public:
    			Delegate() = delete;
    			Delegate(int id,std_function_type std_function_func)
    				:m_Handler(nullptr),m_Id(-1)
    			{
    				if (std_function_func == nullptr)
    					return;
    
    				m_Id = id;
    				m_Handler = std_function_func;
    			}
    
    			void Invoke(Args ...args)
    			{
    				if (m_Handler != nullptr)
    				{
    					m_Handler(args...);
    				}
    			}
    
    		private:
    			int m_Id;
    			std_function_type m_Handler;
    		};
    
    	public:
    		int AddDelegate(std_function_type std_function_func)
    		{
    			if (std_function_func == nullptr)
    				return -1;
    			
    			std::shared_ptr<Delegate> pDelegate = std::make_shared<Delegate>(DELEGATE_ID, std_function_func);
    
    #ifdef EVENT_THREAD_SAFETY
    			std::lock_guard<std::mutex> guard_mutex(m_EventMutex);
    #endif // EVENT_THREAD_SAFETY
    
    
    			m_Delegates.insert(std::pair<int, std::shared_ptr<Delegate>>(DELEGATE_ID, pDelegate));
    
    			return DELEGATE_ID++;
    		}
    
    		bool RemoveDelegate(int delegate_id)
    		{
    #ifdef EVENT_THREAD_SAFETY
    			std::lock_guard<std::mutex> guard_mutex(m_EventMutex);
    #endif // EVENT_THREAD_SAFETY
    
    			if (m_Delegates.count(delegate_id) == 0)
    				return false;
    
    			m_Delegates.erase(delegate_id);
    
    			return true;
    		}
    
    
    		int operator += (std_function_type std_function_func)
    		{
    			return AddDelegate(std_function_func);
    		}
    
    		bool operator -= (int delegate_id)
    		{
    			return RemoveDelegate(delegate_id);
    		}
    
    		void Invoke(Args ...args)
    		{
    #ifdef EVENT_THREAD_SAFETY
    			std::lock_guard<std::mutex> guard_mutex(m_EventMutex);
    #endif // EVENT_THREAD_SAFETY
    
    			for (const auto& key : m_Delegates)
    			{
    				key.second->Invoke(args...);
    			}
    		}
    
    		bool Invoke(int delegate_id, Args ...args)
    		{
    #ifdef EVENT_THREAD_SAFETY
    			std::lock_guard<std::mutex> guard_mutex(m_EventMutex);
    #endif // EVENT_THREAD_SAFETY
    
    			if (m_Delegates.count(delegate_id) == 0)
    				return false;
    
    			m_Delegates[delegate_id]->Invoke(args...);
    
    
    			return true;
    		}
    
    	private:
    		std::map<int, std::shared_ptr<Delegate>> m_Delegates;
    
    #ifdef EVENT_THREAD_SAFETY
    
    		std::mutex m_EventMutex;
    #endif // EVENT_THREAD_SAFETY
    	};
    }
    
    
    #endif // !_EVENT_H_
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155

    该类默认是线程安全的,如果不想要线程安全,可以在包含头文件之前定义EVENT_NO_THREAD_SAFETY即可,该类同时也是模板类,支持定义任何返回类型和可变参数的事件类,并通过返回委托函数ID,对委托函数进行删除操作。

    如果感兴趣,可以访问我的个人站:https://www.stubbornhuang.com/

  • 相关阅读:
    索引介绍及索引的分类
    openEuler 22.03LTS版本安装内核调试镜像vmlinux
    1000道最新高频Java面试题,覆盖25个技术栈,从底层原理到架构
    c: 关于大小端存储
    Java - 位运算的基本原理和用途
    推荐系统中的特征工程
    BSA牛血清白蛋白修饰Fe3O4纳米颗粒 BSA-MION
    第2-3-6章 打包批量下载附件的接口开发-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss
    C# 人像卡通化 Onnx photo2cartoon
    Python滑动窗口算法:滑动窗口算法(4 by 4 sliding window price)
  • 原文地址:https://blog.csdn.net/HW140701/article/details/127647414