我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C++使用标准库实现事件和委托以及信号和槽机制

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

从上述代码可以看出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++标准库内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C++使用标准库实现事件和委托以及信号和槽机制

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C++使用标准库实现事件和委托以及信号和槽机制

这篇文章主要为大家详细介绍了C++如何使用标准库实现事件和委托以及信号和槽机制,文中的示例代码讲解详细,具有一定的借鉴价值,需要的可以参考一下
2022-11-13

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录