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{}; }; }
一、語言層面的一些好玩的東東
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行,有一個是靜態函數,第二個則是一個函數模板,函數體里分別聲明定義了兩個靜態變量,我們討論的就是函數模板里的靜態變量??聪旅娴睦樱?/p>
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.rzrgm.cn/catch/p/3507883.html
仔細看完,應該就知道為什么會有move和forward,以及它們是在什么時候起到什么樣的作用。
3、using
using不就是使用命名空間嗎,這有什么好說的??聪旅娴拇a(源文件第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的標準呢?有需求在原來代碼上修改,增加就行了?;蛟S這也就導致很多程序員就算是不去學新東西也能憑借經驗繼續工作。本來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~~

浙公網安備 33010602011771號