前一篇发布出来之后,我看着阅读量还是挺多的,就是评论和给意见的一个都没有,或许各位看官就跟我一样,看帖子从不回复,只管看就行了。毕竟大家都有公务在身,没太多时间,可以理解。不过没关系,我是不是可以直接想象为我写的东西还挺不错的,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 < ; } 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 = ;
virtual void clear() noexcept = ;
}; 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 = ;
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+);
} 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行)

     static std::size_t next_type() noexcept {
static std::size_t counter = ;
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+);
} if(!handlers[type]) {
handlers[type] = std::make_unique<Handler<E>>();
} return static_cast<Handler<E>&>(*handlers[type]);
}

  1.1、static

    其实说到static,大家看到这里的人应该都不会陌生,比如static函数,static变量等等。也都知道他们的特性,我就不过多说了。

    在这里,大家请看第1行——第10行,有一个是静态函数,第二个则是一个函数模板,函数体里分别声明定义了两个静态变量,我们讨论的就是函数模板里的静态变量。看下面的例子:

 template<typename>
static std::size_t event_type() noexcept {
static std::size_t value = ;
return value++;
} int mian(int argc, char* argv[])
{
std::cout << event_type<int>() << std::endl;    //0
std::cout << event_type<int>() << std::endl;   //1
std::cout << event_type<int>() << std::endl;    //2 std::cout << event_type<double>() << std::endl;  //0
std::cout << event_type<double>() << std::endl; //1
std::cout << event_type<double>() << std::endl;  //2
}

    这里的输出结果我已经在代码里注出来了,对于函数模板中的静态局部变量的初始化规则:

    1、函数模板会根据不同的模板参数生成不同的模板函数

      2、每一个模板函数中的静态局部变量只被初始化一次

    也就是说,对于不同的类型,会生成新的模板函数,函数中会重新定义一个静态变量,并初始化。就相当于两个不同的函数里定义了名字一样的静态局部变量,由于作用域关系,这两个静态局部变量是不相干的。上面的例子,大家切记切记。

  1.2、这段代码的意图 

    了解了前面这段,各位看官跟随我的眼珠子,再来看看第12行——第25行。大体上可以判断,这段代码是要创建并返回一个Handle,这个Handle是怎么创建的呢?

    根据模板类型来创建,使用上面提到的模板函数局部静态变量的技巧,可以获得每个模板类型在handles中的索引,如果索引不存在,则创建;如果存在,则返回对应的Handle。如果有看官不理解这其中的奥妙,可以再仔细看看代码揣摩一下(语言表达能力不够,莫怪莫怪)。

    而这儿的模板类型实际上就是上一篇说的事件类型,大家从函数名上也可以看出来。这样做最直接的好处就是,不需要用enum为每个事件打上索引,索引是内建的,而事件本身就是一个struct类型,这样不管是调用还是以后扩展都非常灵活。为了方便理解,贴一段上一篇提到的事件注册代码:

 tcp->on<uvw::ErrorEvent>([](const uvw::ErrorEvent &, uvw::TcpHandle &) {
std::cout << "error " << std::endl;
});

    在此不得不佩服作者的套路之深。吾辈岂有不学之理。

  

  2、std::move() 和 std::forward()

    这两个东西真的是非常头疼。当时也看了很多文章,但是要真的自己写却很难表达出意思。不过今天,找到一篇非常好的文章:http://www.cnblogs.com/catch/p/3507883.html

    仔细看完,应该就知道为什么会有move和forward,以及它们是在什么时候起到什么样的作用。

  3、using

    using不就是使用命名空间吗,这有什么好说的。看下面的代码(源文件第96行——第99行):

         using Listener = std::function<void(E &, T &)>;
using Element = std::pair<bool, Listener>;
using ListenerList = std::list<Element>;
using Connection = typename ListenerList::iterator;

    是不是颠覆了一些看官的三观。其实这已经是C++11中就有的语法,功能和typedef差不多,但是typedef不能用于模板,比如上面的第一行。而using是可以的。举个例子:

1 typedef int MT1;    //和下面一行等价
using MT1 = int; template<typename T>
typedef std::vector<T> MT3; //错误 template<typename T>
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的所有功能,下面上代码:

     template<typename E>
struct Handler final: BaseHandler {
using Listener = std::function<void(E &, T &)>;    //上面已经说过
using Element = std::pair<bool, Listener>;        //这里的pair中的bool是用来标记,该Element是否要删除,false为不删除
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 {             //清空注册列表,publishing用来标志,是否正在处理事件,如果正在处理,则将Element的first标为true
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);    //这里讲onceL和currentL的数据交换,所以onceL在交换之后实际已经没有数据了,从而实现一次性的事件 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; });  //清除onL中first被标记为true的项,与第4行和第15行对应
} private:
bool publishing{false};
ListenerList onceL{};    //保存一次性事件函数列表
ListenerList onL{};      //保存长期事件函数列表
};

    接下来把目光移动到第63行,如果你觉得没什么异常,那就可以跳过了。如果感觉不对劲,也可以很明显看出来是对类成员的初始化操作,但是这种初始化操作不常见,在C++11中,被称作为列表初始化,详细可以参看:http://zh.cppreference.com/w/cpp/language/list_initialization

    

    emitter.hpp文件去除了libuv相关的依赖后,是可以单独拿出来使用的,可以用于单线程回调。但是值得注意的是该类不是线程安全的,另外它也不适用于异步事件,以后我有时间可以把它改造一下,到时候再和各位分享。

    

三、下一篇

  本来说这一篇可能会等个几天才能写出来的,晚上吃了饭就开始写了,已经快12点了,还挺快的。得赶快睡觉了。

  下一篇目前暂定说一下UnderlyingType 和 Resource,如果篇幅短的话再加点其他的东西。

  这次可能就真得等个几天了,KeKe~~

    

UVW源码漫谈(二)的更多相关文章

  1. UVW源码漫谈(三)

    咱们继续看uvw的源码,这次看的东西比较多,去除底层的一些东西,很多代码都是连贯的,耦合度也比较高了.主要包括下面几个文件的代码: underlying_type.hpp resource.hpp l ...

  2. UVW源码漫谈(四)

    十一假期后就有点懒散,好长时间都没想起来写东西了.另外最近在打LOL的S赛.接触LOL时间不长,虽然平时玩的比较少,水平也相当菜,但是像这种大型的赛事有时间还是不会错过的.主要能够感受到选手们对竞技的 ...

  3. UVW源码漫谈(番外篇)—— Emitter

    这两天天气凉了,苏州这边连续好几天都是淅淅沥沥的下着小雨,今天天气还稍微好点.前两天早上起来突然就感冒了,当天就用了一卷纸,好在年轻扛得住,第二天就跟没事人似的.在这里提醒大家一下,天气凉了,睡凉席的 ...

  4. Fresco 源码分析(二) Fresco客户端与服务端交互(1) 解决遗留的Q1问题

    4.2 Fresco客户端与服务端的交互(一) 解决Q1问题 从这篇博客开始,我们开始讨论客户端与服务端是如何交互的,这个交互的入口,我们从Q1问题入手(博客按照这样的问题入手,是因为当时我也是从这里 ...

  5. Netty 源码(二)NioEventLoop 之 Channel 注册

    Netty 源码(二)NioEventLoop 之 Channel 注册 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) 一 ...

  6. 框架-springmvc源码分析(二)

    框架-springmvc源码分析(二) 参考: http://www.cnblogs.com/leftthen/p/5207787.html http://www.cnblogs.com/leftth ...

  7. Zookeeper 源码(二)序列化组件 Jute

    Zookeeper 源码(二)序列化组件 Jute 一.序列化组件 Jute 对于一个网络通信,首先需要解决的就是对数据的序列化和反序列化处理,在 ZooKeeper 中,使用了Jute 这一序列化组 ...

  8. 一点一点看JDK源码(二)java.util.List

    一点一点看JDK源码(二)java.util.List liuyuhang原创,未经允许进制转载 本文举例使用的是JDK8的API 目录:一点一点看JDK源码(〇) 1.综述 List译为表,一览表, ...

  9. Tomcat源码分析二:先看看Tomcat的整体架构

    Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server.Service.Conn ...

随机推荐

  1. 调用Class.forName()要抛出异常

    今天学JDBC时,用到下面的程序: package bo; import java.sql.Connection; import java.util.ArrayList; import java.ut ...

  2. java 数组去重总结

    如果一个数组中有重复元素,用什么方法可以去重?有其他方法继续更新 一.用List集合实现 int[] str = {5, 6, 6, 6, 8, 8, 7,4}; List<Integer> ...

  3. 【Win10】zip安装MySQL

    1. mysqld初始化时my.ini的第二个默认位置是%windir%/my.ini rem 1.查看此变量对应的目录,在此目录下编辑 my.ini,mysqld会自动找到 echo %WINDIR ...

  4. 【Ubuntu16】apt-get安装MariaDB

    一.Mysql背景信息 Mysql在互联网早期就流行了,追求速度.简单.坚持开源.几乎支持所有操作系统.完全支持多用户.多线程,支持海量数据存储,采用MyISAM.InnoDB两大存储引擎优势互补.但 ...

  5. c语言 进程控制---创建进程 vfork()函数

    #include "stdio.h" #include "unistd.h" #include "sys/types.h" int gvar ...

  6. AC Analysis

    1.从Options---sheet properties—circuit—show all调出来节点编号 :

  7. JavaScript操作DOM节点

    DOM (文档对象模型(Document Object Model)) 文档对象模型(Document Object Model,简称DOM),是W3C组织推荐的处理可扩展标志语言的标准编程接口.在网 ...

  8. [2015-11-23]分享一个批处理脚本,创建iis站点及程序池

    建站批处理 batch_createSites.bat @echo off rem 以管理员身份执行本脚本,可添加多条call 以建立多个站点 call path\to\createSites.bat ...

  9. [2013-01-15]The Little Schemer 学习笔记

    <The Little Schemer> FP编程.lisp入门必备 这书貌似没中文版: 有英文pdf版:完整版下载链接 英文不好的,被前几页噎住的,可以先到这里看翻译好的前言部分 看完人 ...

  10. INotifyPropertyChanged(监听数据),当数据改变时调用

    public class BaseViewModel : INotifyPropertyChanged    {        public event PropertyChangedEventHan ...