C++中事件发布订阅的方法总结和EventEmitter分析

C++中事件发布订阅的方法总结和EventEmitter分析

最朴素的一种方式简析

C++中发布订阅的方法有很多种,
个人认为最朴素的一种方式,也是我编程生涯中最早遇到的方式,简单说一下,这里以一个websocket的客户端链接为例进行说明

有多个任务比如Renderer(UI渲染)、Logger(日志记录)、事件上报(Reporter)三个任务,这三个任务要做的工作都依赖了websocket的执行情况,这里我们就可以使用发布订阅的方式:

WebSocketImpl类

// WebSocket事件通知接口
class WebSocketEventHandler {
virtual void onJoin() = 0;
virtual void onLeave() = 0;
};

WebSocket 类
class WebSocketImpl {
public:
void registerEventHandler(WebSocketEventHandler* handler);
void unRegistEventHandler(WebSocketEventHandler* handler);
void 
private:
std::list<WebSocketEventHandler> event_lists_;
};

Renderer类

class Renderer : public WebSocketEventHandler {
void onJoin() override;
void onLeave() override;
};

Logger类

class Logger : public WebSocketEventHandler {
void onJoin() override;
void onLeave() override;
};

Reporter类

class Reporter : public WebSocketEventHandler {
void onJoin() override;
void onLeave() override;
};

业务逻辑除可能会有类似的代码

int main(void) {
render.registerEventHandler(render);
logger.registerEventHandler(logger);
reporter.registerEventHandler(logger);
whie (true) {
	...
}
render.unRegistEventHandler(render);
logger.unRegistEventHandler(logger);
reporter.unRegistEventHandler(logger);
}

总结

代码冗余比较多。现在很少再看到有这种代码了。

EventEmitter 分析

先上源码

//
//  EventEmitter.hpp
//  EventEmitter
//
//  Created by BlueCocoa on 2016/8/6.
//  Copyright © 2016 BlueCocoa. All rights reserved.
//

#ifndef EVENTEMITTER_HPP
#define EVENTEMITTER_HPP

#include <map>
#include <mutex>
#include <string>
#include <tuple>
#include <type_traits>
#include <vector>
#include <algorithm>
#include <Functor.hpp>

#define Infinity 0

class EventEmitter {
public:
	/**
	*  @brief EventListener
	*
	*  @note  Callable Function, Call once
	*/
	using EventListener = std::tuple<Functor *, bool>;

	/**
	*  @brief Deconstructor
	*/
	~EventEmitter() {
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		std::for_each(events.begin(), events.end(), [](std::pair<std::string, std::vector<EventListener>> pair) {
			std::vector<EventListener>& listeners = pair.second;
			std::for_each(listeners.begin(), listeners.end(), [](EventListener& listener) {
				//delete std::get<0>(listener);
				//std::get<0>(listener) = nullptr;
			});
		});
		events.clear();
	}

	/**
	*  @brief Event setter
	*
	*  @param event  Event name
	*  @param lambda Callback function when event emitted
	*/
	template <typename Function>
	void on(const std::string& event, Function&& lambda) {
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		events[event].emplace_back(new Functor{ std::forward<Function>(lambda) }, false);
	}

	/**
	*  @brief Once event
	*
	*  @param event  Event name
	*  @param lambda Callback function when event emitted
	*/
	template <typename Function>
	void once(const std::string& event, Function&& lambda) {
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		events[event].emplace_back(new Functor{ std::forward<Function>(lambda) }, true);
	}

	/**
	*  @brief Event emitter
	*
	*  @param event  Event name
	*/
	template <typename ... Arg>
	void emit(const std::string& event, Arg&& ... args) {
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		auto event_listeners = events.find(event);
		if (event_listeners == events.end()) return;

		std::vector<Functor *> functors;

		std::vector<EventListener>& listeners = events[event];
		for (auto listener = listeners.begin(); listener != listeners.end();) {
			Functor * on = std::get<0>(*listener);
			bool once = std::get<1>(*listener);
			functors.push_back(on);
			
			if (once) {
				//			delete on;
				//std::get<0>(*listener) = nullptr;
				listener = listeners.erase(listener);
			}
			else
			{
				++listener;
			}
		}
		listeners.shrink_to_fit();

		for (auto &it:functors)
		{
			Functor * on = it;
			if (on) {
				(*on) (std::forward<Arg>(args)...);
			}
		}
	}

	/**
	*  @brief Number of listeners
	*
	*  @param event  Event name
	*/
	int listenerCount(const std::string& event) {
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		auto event_listeners = events.find(event);
		if (event_listeners == events.end()) return 0;
		return events[event].size();;
	}
	/**
	*  @brief Remove all listeners
	*
	*  @param event  Event name
	*/
	void removeAllListeners(const std::string& event){
		std::unique_lock<std::recursive_mutex> locker(_events_mtx);
		std::vector<EventListener>& listeners = events[event];
		std::for_each(listeners.begin(), listeners.end(), [](EventListener& listener) {
			//delete std::get<0>(listener);
			//std::get<0>(listener) = nullptr;
		});

		listeners.clear();
		events.erase(event);
	}

	void setMaxListeners(int listenerCount)
	{

	}


protected:
	/**
	*  @brief Constructor
	*/
	EventEmitter() {
	};

	/**
	*  @brief Event name - EventListener
	*/
	std::map<std::string, std::vector<EventListener>> events;

private:
	/**
	*  @brief Mutex for events
	*/
	std::recursive_mutex _events_mtx;
};

#endif /* EVENTEMITTER_HPP */

解析

  • 类中的构造函数是protected,那就表明我们使用的时候无法用new直接构造,我们要通过new一个子类来实现
  • emit和on必须是同一个应用实体
  • emit和on的操作是线程安全的
  • 订阅事件可以大于1个
  • 析构函数会主动清理订阅内容,使用时候注意事件处理的生命周期即可。

例子

还是以上面举的例子为例:
有多个任务比如Renderer(UI渲染)、Logger(日志记录)、事件上报(Reporter)三个任务,这三个任务要做的工作都依赖了websocket的执行情况

class WebSocketImpl : public EventEmitter {
	void onJoin() {
		emit("ws-joined", this);
	}
   void onLeave() {
   		emit("ws-leaved", this, ...);
   }
};

业务处理部分

int main(void) {
	WebSocketImpl* impl = new WebSocketImpl;
	impl->run();
	/*订阅事件*/
	impl.on("ws-joined", [=](WebSocketImpl* impl) {
		//report ...
	});
	  impl.on("ws-joined", [=](WebSocketImpl* impl) {
		//logger ...
	});
	  impl.on("ws-joined", [=](WebSocketImpl* impl) {
		//renderer ...
	});
	
	...
	impl.on("ws-leaved", [=](WebSocketImpl* impl, ...) {
		//report ...
	});
	  impl.on("ws-leaved", [=](WebSocketImpl* impl, ...) {
		//logger ...
	});
	  impl.on("ws-leaved", [=](WebSocketImpl* impl, ...) {
		//renderer ...
	});
  
}

总结

  • 代码更加简洁,冗余较少
  • 减少了代码的耦合度,省掉了WebSocketEventHandler这个中间代理

总结再总结

当然还有很多这样的技术可以进一步深入研究
比如

  • Qt的Signal/Slot技术
  • Boost中的sigslot实现等等,
  • 还有一些rxjava、eventbus中的的一些封装更加完善的一用,都值得我们去学习和玩味。

但说了这么多,其实再学习的时候,个人觉得要注意两个问题就可以放心使用了。

  1. 是否支持线程安全
  2. 是否是同步调用,上面的两个例子都是同步的
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值