UVW源码漫谈(二)
前一篇发布出来之后,我看着阅读量还是挺多的,就是评论和给意见的一个都没有,或许各位看官就跟我一样,看帖子从不回复,只管看就行了。毕竟大家都有公务在身,没太多时间,可以理解。不过没关系,我是不是可以直接想象为我写的东西还挺不错的,KeKe~~。
这一篇介绍一下源代码 ./src/uvw/emitter.hpp 里的东东。由于代码量实在比较大,我就折叠起来了,不然各位看官手指头滚上滚下的,太累了。之后我就写到哪儿贴哪儿的代码,注个大概源代码的位置,有兴趣自己打开源代码对照看看,是不是看的就比较舒服点了。
#pragma once#include <type_traits> #include <functional> #include <algorithm> #include <utility> #include <cstddef> #include <vector> #include <memory> #include <list> #include <uv.h>namespace uvw {/*** @brief The ErrorEvent event.** Custom wrapper around error constants of `libuv`.*/ struct ErrorEvent {template<typename U, typename = std::enable_if_t<std::is_integral<U>::value>>explicit ErrorEvent(U val) noexcept: ec{static_cast<int>(val)}{}/*** @brief Returns the `libuv` error code equivalent to the given platform dependent error code.** It returns:* * POSIX error codes on Unix (the ones stored in errno).* * Win32 error codes on Windows (those returned by GetLastError() or WSAGetLastError()).** If `sys` is already a `libuv` error code, it is simply returned.** @param sys A platform dependent error code.* @return The `libuv` error code equivalent to the given platform dependent error code.*/static int translate(int sys) noexcept {return uv_translate_sys_error(sys);}/*** @brief Returns the error message for the given error code.** Leaks a few bytes of memory when you call it with an unknown error code.** @return The error message for the given error code.*/const char * what() const noexcept { return uv_strerror(ec); }/*** @brief Returns the error name for the given error code.** Leaks a few bytes of memory when you call it with an unknown error code.** @return The error name for the given error code.*/const char * name() const noexcept { return uv_err_name(ec); }/*** @brief Gets the underlying error code, that is an error constant of `libuv`.* @return The underlying error code.*/int code() const noexcept { return ec; }/*** @brief Checks if the event contains a valid error code.* @return True in case of success, false otherwise.*/explicit operator bool() const noexcept { return ec < 0; }private:const int ec; };/*** @brief Event emitter base class.** Almost everything in `uvw` is an event emitter.<br/>* This is the base class from which resources and loops inherit.*/ template<typename T> class Emitter {struct BaseHandler {virtual ~BaseHandler() noexcept = default;virtual bool empty() const noexcept = 0;virtual void clear() noexcept = 0;};template<typename E>struct Handler final: BaseHandler {using Listener = std::function<void(E &, T &)>;using Element = std::pair<bool, Listener>;using ListenerList = std::list<Element>;using Connection = typename ListenerList::iterator;bool empty() const noexcept override {auto pred = [](auto &&element){ return element.first; };return std::all_of(onceL.cbegin(), onceL.cend(), pred) &&std::all_of(onL.cbegin(), onL.cend(), pred);}void clear() noexcept override {if(publishing) {auto func = [](auto &&element){ element.first = true; };std::for_each(onceL.begin(), onceL.end(), func);std::for_each(onL.begin(), onL.end(), func);} else {onceL.clear();onL.clear();}}Connection once(Listener f) {return onceL.emplace(onceL.cend(), false, std::move(f));}Connection on(Listener f) {return onL.emplace(onL.cend(), false, std::move(f));}void erase(Connection conn) noexcept {conn->first = true;if(!publishing) {auto pred = [](auto &&element){ return element.first; };onceL.remove_if(pred);onL.remove_if(pred);}}void publish(E event, T &ref) {ListenerList currentL;onceL.swap(currentL);auto func = [&event, &ref](auto &&element) {return element.first ? void() : element.second(event, ref);};publishing = true;std::for_each(onL.rbegin(), onL.rend(), func);std::for_each(currentL.rbegin(), currentL.rend(), func);publishing = false;onL.remove_if([](auto &&element){ return element.first; });}private:bool publishing{false};ListenerList onceL{};ListenerList onL{};};static std::size_t next_type() noexcept {static std::size_t counter = 0;return counter++;}template<typename>static std::size_t event_type() noexcept {static std::size_t value = next_type();return value;}template<typename E>Handler<E> & handler() noexcept {std::size_t type = event_type<E>();if(!(type < handlers.size())) {handlers.resize(type+1);}if(!handlers[type]) {handlers[type] = std::make_unique<Handler<E>>();}return static_cast<Handler<E>&>(*handlers[type]);}protected:template<typename E>void publish(E event) {handler<E>().publish(std::move(event), *static_cast<T*>(this));}public:template<typename E>using Listener = typename Handler<E>::Listener;/*** @brief Connection type for a given event type.** Given an event type `E`, `Connection<E>` is the type of the connection* object returned by the event emitter whenever a listener for the given* type is registered.*/template<typename E>struct Connection: private Handler<E>::Connection {template<typename> friend class Emitter;Connection() = default;Connection(const Connection &) = default;Connection(Connection &&) = default;Connection(typename Handler<E>::Connection conn): Handler<E>::Connection{std::move(conn)}{}Connection & operator=(const Connection &) = default;Connection & operator=(Connection &&) = default;};virtual ~Emitter() noexcept {static_assert(std::is_base_of<Emitter<T>, T>::value, "!");}/*** @brief Registers a long-lived listener with the event emitter.** This method can be used to register a listener that is meant to be* invoked more than once for the given event type.<br/>* The Connection object returned by the method can be freely discarded. It* can be used later to disconnect the listener, if needed.** Listener is usually defined as a callable object assignable to a* `std::function<void(const E &, T &)`, where `E` is the type of the event* and `T` is the type of the resource.** @param f A valid listener to be registered.* @return Connection object to be used later to disconnect the listener.*/template<typename E>Connection<E> on(Listener<E> f) {return handler<E>().on(std::move(f));}/*** @brief Registers a short-lived listener with the event emitter.** This method can be used to register a listener that is meant to be* invoked only once for the given event type.<br/>* The Connection object returned by the method can be freely discarded. It* can be used later to disconnect the listener, if needed.** Listener is usually defined as a callable object assignable to a* `std::function<void(const E &, T &)`, where `E` is the type of the event* and `T` is the type of the resource.** @param f Avalid listener to be registered.* @return Connection object to be used later to disconnect the listener.*/template<typename E>Connection<E> once(Listener<E> f) {return handler<E>().once(std::move(f));}/*** @brief Disconnects a listener from the event emitter.* @param conn A valid Connection object*/template<typename E>void erase(Connection<E> conn) noexcept {handler<E>().erase(std::move(conn));}/*** @brief Disconnects all the listeners for the given event type.*/template<typename E>void clear() noexcept {handler<E>().clear();}/*** @brief Disconnects all the listeners.*/void clear() noexcept {std::for_each(handlers.begin(), handlers.end(),[](auto &&hdlr){ if(hdlr) { hdlr->clear(); } });}/*** @brief Checks if there are listeners registered for the specific event.* @return True if there are no listeners registered for the specific event,* false otherwise.*/template<typename E>bool empty() const noexcept {std::size_t type = event_type<E>();return (!(type < handlers.size()) ||!handlers[type] ||static_cast<Handler<E>&>(*handlers[type]).empty());}/*** @brief Checks if there are listeners registered with the event emitter.* @return True if there are no listeners registered with the event emitter,* false otherwise.*/bool empty() const noexcept {return std::all_of(handlers.cbegin(), handlers.cend(),[](auto &&hdlr){ return !hdlr || hdlr->empty(); });}private:std::vector<std::unique_ptr<BaseHandler>> handlers{}; };}
emitter
一、语言层面的一些好玩的东东
1、(源文件大概 第161行 —— 第185行)
1 static std::size_t next_type() noexcept { 2 static std::size_t counter = 0; 3 return counter++; 4 } 5 6 template<typename> 7 static std::size_t event_type() noexcept { 8 static std::size_t value = next_type(); 9 return value; 10 } 11 12 template<typename E> 13 Handler<E> & handler() noexcept { 14 std::size_t type = event_type<E>(); 15 16 if(!(type < handlers.size())) { 17 handlers.resize(type+1); 18 } 19 20 if(!handlers[type]) { 21 handlers[type] = std::make_unique<Handler<E>>(); 22 } 23 24 return static_cast<Handler<E>&>(*handlers[type]); 25 }
1.1、static
其实说到static,大家看到这里的人应该都不会陌生,比如static函数,static变量等等。也都知道他们的特性,我就不过多说了。
在这里,大家请看第1行——第10行,有一个是静态函数,第二个则是一个函数模板,函数体里分别声明定义了两个静态变量,我们讨论的就是函数模板里的静态变量。看下面的例子:
1 template<typename> 2 static std::size_t event_type() noexcept { 3 static std::size_t value = 0; 4 return value++; 5 } 6 7 int mian(int argc, char* argv[]) 8 { 9 std::cout << event_type<int>() << std::endl; //0 10 std::cout << event_type<int>() << std::endl; //1 11 std::cout << event_type<int>() << std::endl; //2 12 13 std::cout << event_type<double>() << std::endl; //0 14 std::cout << event_type<double>() << std::endl; //1 15 std::cout << event_type<double>() << std::endl; //2 16 }
这里的输出结果我已经在代码里注出来了,对于函数模板中的静态局部变量的初始化规则:
1、函数模板会根据不同的模板参数生成不同的模板函数
2、每一个模板函数中的静态局部变量只被初始化一次
也就是说,对于不同的类型,会生成新的模板函数,函数中会重新定义一个静态变量,并初始化。就相当于两个不同的函数里定义了名字一样的静态局部变量,由于作用域关系,这两个静态局部变量是不相干的。上面的例子,大家切记切记。
1.2、这段代码的意图
了解了前面这段,各位看官跟随我的眼珠子,再来看看第12行——第25行。大体上可以判断,这段代码是要创建并返回一个Handle,这个Handle是怎么创建的呢?
根据模板类型来创建,使用上面提到的模板函数局部静态变量的技巧,可以获得每个模板类型在handles中的索引,如果索引不存在,则创建;如果存在,则返回对应的Handle。如果有看官不理解这其中的奥妙,可以再仔细看看代码揣摩一下(语言表达能力不够,莫怪莫怪)。
而这儿的模板类型实际上就是上一篇说的事件类型,大家从函数名上也可以看出来。这样做最直接的好处就是,不需要用enum为每个事件打上索引,索引是内建的,而事件本身就是一个struct类型,这样不管是调用还是以后扩展都非常灵活。为了方便理解,贴一段上一篇提到的事件注册代码:
1 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) { 2 std::cout << "error " << std::endl; 3 });
在此不得不佩服作者的套路之深。吾辈岂有不学之理。
2、std::move() 和 std::forward()
这两个东西真的是非常头疼。当时也看了很多文章,但是要真的自己写却很难表达出意思。不过今天,找到一篇非常好的文章:http://www.cnblogs.com/catch/p/3507883.html
仔细看完,应该就知道为什么会有move和forward,以及它们是在什么时候起到什么样的作用。
3、using
using不就是使用命名空间吗,这有什么好说的。看下面的代码(源文件第96行——第99行):
1 using Listener = std::function<void(E &, T &)>; 2 using Element = std::pair<bool, Listener>; 3 using ListenerList = std::list<Element>; 4 using Connection = typename ListenerList::iterator;
是不是颠覆了一些看官的三观。其实这已经是C++11中就有的语法,功能和typedef差不多,但是typedef不能用于模板,比如上面的第一行。而using是可以的。举个例子:
1 typedef int MT1; //和下面一行等价 2 using MT1 = int; 3 4 template<typename T> 5 typedef std::vector<T> MT3; //错误 6 7 template<typename T> 8 using MT4 = std::vector<T>; //正确
再看刚刚第4行:using Connection = typename ListenerList::iterator; 这里怎么会有一个typename? 以往在我们在写模板时会用到typename 和 class,比如上面的例子,typename是可以换成class的。而这里是用来告诉编译器,ListenerList::iterator 是一个类型。
这里说一些其他的,相信大家很多都在公司上班吧,除非有自由职业编码者?其实在公司里,很多都是历史遗留下来的代码,而且这些代码也经过了时间的验证,经历过很多工程师的优化修改,所以这些代码除非确认出现问题,否则基本不会修改。以后来到公司的,为了适应以前的编码方式和编译器版本,这些语法基本就很难见到了。所以即使有新的标准出来,很多人也不会去关心,去学习。比如公司指定编码环境为WIN7+VS2010,你说2010的编译器怎么可能包含C++11的标准呢?有需求在原来代码上修改,增加就行了。或许这也就导致很多程序员就算是不去学新东西也能凭借经验继续工作。本来C++98就已经包含了C++作为一种面向对象的计算机语言所需要包含的必须的特性了,所谓封装,多态,继承再加上一些标准库,确实对于大多数需要效率的程序已经够用了,这可能也是C++设计的初衷吧。何必再多此一举,学这学那,说到底C++也只是个工具而已,说不定以后就被淘汰了呢!
这种现象也不能说对与错,另外C++17标准也已经出来了,很多人抱怨C++11还没搞明白呢,C++17就来了。知乎百度上对新标准的讨论也是一浪高过一浪,基本可以总结出一点:不管是大牛还是菜鸟,大家还都是对C++很有感情的。所以对于咱们天天用C++的人来说,不用管以后C++发展的怎么样,只要自己用着舒服就行了,但是前提是得先学,学了才有选择的余地,才游刃有余。
说了一堆废话,咱们继续。KeKe~~
4、其他零碎的东东
#pragma once :很明显,在windows编程中,处理头文件相互包含时,就用这个,而在C标准中则是用 #ifndef ... #define ... #endif 。其实C++标准是支持这个东西的,但在Linux环境下很少有人用,大家都比较推崇那种预编译指令。还有一些原因就是有些编译器不支持,大家可以查看 http://zh.cppreference.com/w/cpp/preprocessor/impl 的说明和编译器支持列表,现在大部分编译器都会支持,所以大胆用就是了,如果恰巧不能用那就换过来呗,也不麻烦。
noexcept:指定该函数不抛出异常,比如 int* p = new (std::nothrow) int; 则指定new失败后不抛出异常,而是返回NULL。(这貌似和noexcept不相干,想到什么写什么喽)
override:指定派生类中需要被重写的虚函数。
explicit:阻止类的隐式类型转换。
二、Emitter类
这个类的功能,就是用于事件注册和发送,uvw中很多类都会继承Emitter,用来响应事件。
基本流程就是,调用on()或once()保存注册事件函数,当libuv有回调时,将数据打包为事件结构体,直接调用publish(),在publish()中再调用注册的相应的事件函数。
很多东西都已经在上面说过了,我就仅介绍一下Handle,Handle是定义在Emitter中的内嵌类。几乎实现了Emitter的所有功能,下面上代码:
1 template<typename E> 2 struct Handler final: BaseHandler { 3 using Listener = std::function<void(E &, T &)>; //上面已经说过 4 using Element = std::pair<bool, Listener>; //这里的pair中的bool是用来标记,该Element是否要删除,false为不删除 5 using ListenerList = std::list<Element>; 6 using Connection = typename ListenerList::iterator; 7 8 bool empty() const noexcept override { 9 auto pred = [](auto &&element){ return element.first; }; 10 11 return std::all_of(onceL.cbegin(), onceL.cend(), pred) && 12 std::all_of(onL.cbegin(), onL.cend(), pred); 13 } 14 15 void clear() noexcept override { //清空注册列表,publishing用来标志,是否正在处理事件,如果正在处理,则将Element的first标为true 16 if(publishing) { 17 auto func = [](auto &&element){ element.first = true; }; 18 std::for_each(onceL.begin(), onceL.end(), func); 19 std::for_each(onL.begin(), onL.end(), func); 20 } else { 21 onceL.clear(); 22 onL.clear(); 23 } 24 } 25 26 Connection once(Listener f) { //注册一次性事件 27 return onceL.emplace(onceL.cend(), false, std::move(f)); 28 } 29 30 Connection on(Listener f) { //注册长期事件 31 return onL.emplace(onL.cend(), false, std::move(f)); 32 } 33 34 void erase(Connection conn) noexcept { 35 conn->first = true; 36 37 if(!publishing) { 38 auto pred = [](auto &&element){ return element.first; }; 39 onceL.remove_if(pred); 40 onL.remove_if(pred); 41 } 42 } 43 44 void publish(E event, T &ref) { 45 ListenerList currentL; 46 onceL.swap(currentL); //这里讲onceL和currentL的数据交换,所以onceL在交换之后实际已经没有数据了,从而实现一次性的事件 47 48 auto func = [&event, &ref](auto &&element) { 49 return element.first ? void() : element.second(event, ref); 50 }; 51 52 publishing = true; //标记正在处理事件 53 54 std::for_each(onL.rbegin(), onL.rend(), func); 55 std::for_each(currentL.rbegin(), currentL.rend(), func); 56 57 publishing = false; //标记处理事件结束 58 59 onL.remove_if([](auto &&element){ return element.first; }); //清除onL中first被标记为true的项,与第4行和第15行对应 60 } 61 62 private: 63 bool publishing{false}; 64 ListenerList onceL{}; //保存一次性事件函数列表 65 ListenerList onL{}; //保存长期事件函数列表 66 };
接下来把目光移动到第63行,如果你觉得没什么异常,那就可以跳过了。如果感觉不对劲,也可以很明显看出来是对类成员的初始化操作,但是这种初始化操作不常见,在C++11中,被称作为列表初始化,详细可以参看:http://zh.cppreference.com/w/cpp/language/list_initialization
emitter.hpp文件去除了libuv相关的依赖后,是可以单独拿出来使用的,可以用于单线程回调。但是值得注意的是该类不是线程安全的,另外它也不适用于异步事件,以后我有时间可以把它改造一下,到时候再和各位分享。
三、下一篇
本来说这一篇可能会等个几天才能写出来的,晚上吃了饭就开始写了,已经快12点了,还挺快的。得赶快睡觉了。
下一篇目前暂定说一下UnderlyingType 和 Resource,如果篇幅短的话再加点其他的东西。
这次可能就真得等个几天了,KeKe~~
转载于:https://www.cnblogs.com/yxfangcs/p/7541842.html
UVW源码漫谈(二)相关推荐
- UVW源码漫谈(一)
博客园是个非常好的学习知识的地方,相信有很多人跟我一样,园龄3年,从博客园不知道拷了多少代码,看了多少博客,自己却一篇博客都没写过.真是罪过. 这次准备写几篇关于这个项目源码的阅读和理解的文章,大家一 ...
- UVW源码漫谈(三)
咱们继续看uvw的源码,这次看的东西比较多,去除底层的一些东西,很多代码都是连贯的,耦合度也比较高了.主要包括下面几个文件的代码: underlying_type.hpp resource.hpp l ...
- 【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )
Android 事件分发 系列文章目录 [Android 事件分发]事件分发源码分析 ( 驱动层通过中断传递事件 | WindowManagerService 向 View 层传递事件 ) [Andr ...
- LambdaMART简介——基于Ranklib源码(二 Regression Tree训练)
LambdaMART简介--基于Ranklib源码(二 Regression Tree训练) 上一节中介绍了 λ λ 的计算,lambdaMART就以计算的每个doc的 λ λ 值作为label ...
- mybatis源码阅读(二):mybatis初始化上
转载自 mybatis源码阅读(二):mybatis初始化上 1.初始化入口 //Mybatis 通过SqlSessionFactory获取SqlSession, 然后才能通过SqlSession与 ...
- 电竞比分源码/免买分源码/可二开/支持最新PHP7.3/LOL,王者,吃鸡等等电竞比分源码
简介: [独家分享]比分源码/免买分源码/可二开/支持PHP7.3 OS6.5 到7.5 推荐7.4 Nginx -Tengine2.2.3 MySQL 8.0.16 PHP 7.3 Redis 5. ...
- 一点一点看JDK源码(二)java.util.List
一点一点看JDK源码(二)java.util.List liuyuhang原创,未经允许进制转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 List译为表,一览表, ...
- Spring Cloud 2.2.2 源码之二十九nacos客户端获取配置原理四
Spring Cloud 2.2.2 源码之二十九nacos客户端获取配置原理四 MetricsHttpAgent的httpGet ServerHttpAgent的httpGet HttpSimple ...
- SpringBoot源码分析(二)之自动装配demo
SpringBoot源码分析(二)之自动装配demo 文章目录 SpringBoot源码分析(二)之自动装配demo 前言 一.创建RedissonTemplate的Maven服务 二.创建测试服务 ...
最新文章
- GHOST还原教程详细
- tensflower官方测试案例_大数据性能测试介绍
- 亚马逊 开发者api 调用_关于微信API:常用微信API文档整理
- antd的 input有下拉_解决antd 下拉框 input [defaultValue] 的值的问题
- 使用OpenCV在Python中进行人脸和眼睛检测
- 需求分析——调研需求时如何调查系统相关者?
- 分治法求最大和最小值
- 【Kettle】Kitchen和Pan的命令行参数
- 解决Spring Boot启动项目Tomcat不能访问80端口的问题
- 前台数据数组转化为json数据
- SpringBoot整合mybatis 配置文件备份
- 使用PyCharm官方中文语言包汉化PyCharm
- 【汇编】从键盘输入16位有符号数x,y,z,w,编写程序实现:w=x+ y+24 - z,结果存放在w中, 并显示结果。
- 程序猿 C语言入门 颈椎,【计时器小工具】70行代码让你远离颈椎病,致所有的程序猿们...
- Centos修复boot分区
- csgo连接到任意官方服务器失败删除文件,CSGO连接到官方任意服务器失败怎么办...
- 在网址前加神秘字母,让你打开新世界(z)
- 自动驾驶数据之争,走向合规
- 前端理解base64
- 头条搬砖最新实操玩法
热门文章
- XStudioSinger· 歌手快速上手
- 智能停车场系统的需求应用分析
- 以太坊智能合约--简单众筹项目
- 2022年安全生产监管人员考试题及答案
- Postgresql 14 主从配置
- 表达式求值:从“加减”到“带括号的加减乘除”的实践过程
- 优思学院|单件流为什么是精益生产管理的理想状态?
- SAP HANA跟我学HANA系列之创建分析视图一
- windows中Esc与CapsLock、Alt与Ctrl调换位置
- qemu服务器虚拟机与主机通讯,虚拟机:QEMU虚拟机和主机无线网络通讯设置