C++: implementación de eventos y mecanismos de delegados, señales y ranuras utilizando la biblioteca estándar

En el desarrollo diario de programas, a menudo nos encontramos con los siguientes problemas prácticos:

  • Por ejemplo, cuando se descarga un archivo, se envía un correo electrónico o una notificación de WeChat para informar al usuario;
  • Por ejemplo, cuando se hace clic en un botón, se ejecuta la lógica empresarial correspondiente;
  • Por ejemplo, cuando el monto del usuario es inferior a un umbral, se le notifica al usuario que recargue a tiempo;

etc.

Estos requisitos comerciales en realidad corresponden al patrón de observador. Cuando el estado de un objeto cambia o alcanza una determinada condición, se notificará a todos los objetos de observador. El patrón de observador implementa un diseño de estructura de software débilmente acoplado a través del diseño orientado a objetos.

Los delegados y eventos en C# y el mecanismo de ranuras y señales de Qt siguen este patrón de diseño. En el proceso de uso de C# y Qt, a menudo me lamento por qué la biblioteca estándar de C++ no viene con este tipo de clase nativa para un desarrollo rápido (aunque boost sí lo hace), por lo que en este artículo usamos plantillas de C++ para implementar un C++ simple pero suficiente. clase de herramienta de eventos.

1.Net delegados y eventos

Veamos primero el ejemplo de delegado en 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();
	}
}

En el código anterior, puede ver si el delegado de C# es muy similar a la declaración de puntero de función de C++: primero, declare una forma de función que indique el valor de retorno y los parámetros formales, luego pase una función que se ajuste a esta forma como parámetro y, finalmente, Realice llamadas, similares a las declaraciones de puntero de función de C y a las de C++ std::function.

Después de analizar la delegación, veamos un ejemplo de un evento,

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);
	}
}

En el código anterior, declaramos un OnInsufficientBalanceevento. Este evento se activa cuando la cuenta de usuario es inferior a 100. La función de activación utiliza un correo electrónico para notificar al usuario.

Señales y ranuras de 2 Qt

El mecanismo de señales y ranuras de Qt es un mecanismo de observador implementado por Qt, que puede activar métodos de ranura vinculados a través de señales.

Una señal es un evento que se emite bajo circunstancias específicas. Por ejemplo, la señal más común de PushButton es la señal clicked() emitida cuando se hace clic con el mouse.
La ranura es una función de respuesta a una señal. Las funciones de ranura se pueden asociar con una señal y, cuando se emite la señal, la función de ranura asociada se ejecuta automáticamente.

Cuando se hace clic en un botón, Qt emite una señal de que se hizo clic en el botón y luego activa el método de ranura personalizado del desarrollador vinculado a la señal.

Los métodos de señal y ranura de Qt son aproximadamente los mismos que los delegados y eventos de .Net, donde las señales corresponden a eventos y las funciones de ranura corresponden a delegados.

El código de muestra es el siguiente:

button1 = new QPushButton("close",this);//创建按钮,指定父对象
button2 = new QPushButton("print",this);//创建按钮,指定父对象

connect(button1,&QPushButton::clicked,this,&QWidget::close);
connect(button2,&QPushButton::clicked,this,[](){
    
    
        qDebug() << "关闭成功";//打印关闭成功
    });

3 Delegados y eventos en Duilib

También hay implementaciones simples de delegados y eventos en Duilib, y podemos ver las implementaciones correspondientes en UIDelegate.hy .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

Del código fuente anterior de la implementación del mecanismo de delegación y evento de Duilib, podemos ver la idea de implementación completa.Al CEventSourcecrear un evento, MakeDelegateuna función delegada vinculada al evento se construye a través de una función CDelegate<O, T>, y la forma de esta función delegada solo puede estar en void(void*)forma de . Luego, CEventSourcesobrecargando operadores +=y -=agregando y eliminando funciones delegadas. El método de Duilib debería ser el prototipo de evento y delegado más simple, pero la desventaja es que los eventos solo se pueden vincular a funciones de delegado de forma fija.

4 Implementación simple del mecanismo de activación de eventos usando la biblioteca estándar de C++

Sección 3 Los delegados y eventos de Duilib no pueden personalizar la forma de la función delegada vinculada al evento. En esta sección, utilizamos la biblioteca estándar de C ++ para implementar el mecanismo de eventos y podemos personalizar la forma de la función de vinculación de eventos.

El código específico es el siguiente:

Evento.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_

En el código anterior, creamos una plantilla template<typename ReturnType, typename ...Args>para la clase de evento y usamos la plantilla de parámetros variables para personalizar la lista de parámetros de la función delegada para el enlace de eventos, que puede aceptar múltiples tipos diferentes de parámetros. Utilice la función delegada que almacena el evento vinculado y sobrecargue el operador para agregar la función delegada.Eventtypename ...Argsstd::vectorstd::function<ReturnType(Args...)>+=

Ejemplos de uso de las clases de herramientas de eventos anteriores Eventson los siguientes:

#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;
}

Resultados de:

Button Click
Example Click
Lambda Click

Debido a std::functionsus súper características, podemos vincular funciones estáticas, funciones de miembros de clase y funciones anónimas a eventos.

5 Resumen

En este artículo, presentamos brevemente los eventos y delegados de .Net y las señales y ranuras de Qt, y luego ampliamos la clase de evento simple personalizada introduciendo la implementación simple de eventos y delegados en Duilib. Dicha implementación es relativamente simple, Eventpero Contiene las ideas centrales de la práctica de eventos. Tengo una nueva experiencia en el uso de clases de plantillas y plantillas de parámetros variables.

6 Organizar los proyectos de código abierto modificados.

Recientemente, tuve tiempo para expandir y organizar el código según la Sección 4 y abrirlo en Github. La dirección del proyecto es: https://github.com/HW140701/TinyEvent. Los estudiantes interesados ​​pueden Star Or Fork.

La clase de evento de evento modificada es la siguiente

/*
Author:StubbornHuang
Data:2023.1.31
Email:[email protected]
*/


#ifndef _EVENT_H_
#define _EVENT_H_

#include <functional>
#include <map>
#include <type_traits>

#ifndef EVENT_NO_THREAD_SAFETY
#define EVENT_THREAD_SAFETY
#endif // !EVENT_NO_THREAD_SAFETY

#ifdef EVENT_THREAD_SAFETY
#include <atomic>
#include <mutex>
#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_

Esta clase es segura para subprocesos de forma predeterminada. Si no desea seguridad para subprocesos, puede definirla antes de incluir el archivo de encabezado. EVENT_NO_THREAD_SAFETYEsta clase también es una clase de plantilla y admite la definición de clases de eventos con cualquier tipo de retorno y parámetros variables. y devuelve el ID de la función delegada por , elimina la función delegada.

Si está interesado, puede visitar mi sitio web personal: https://www.stubbornhuang.com/

Supongo que te gusta

Origin blog.csdn.net/HW140701/article/details/127647414
Recomendado
Clasificación