UVW源碼漫談(三)

来源:http://www.cnblogs.com/yxfangcs/archive/2017/09/28/7607806.html
-Advertisement-
Play Games

咱們繼續看uvw的源碼,這次看的東西比較多,去除底層的一些東西,很多代碼都是連貫的,耦合度也比較高了。主要包括下麵幾個文件的代碼: underlying_type.hpp resource.hpp loop.hpp handle.hpp stream.hpp tcp.hpp 代碼我就不都貼出了,說到 ...


咱們繼續看uvw的源碼,這次看的東西比較多,去除底層的一些東西,很多代碼都是連貫的,耦合度也比較高了。主要包括下麵幾個文件的代碼:

underlying_type.hpp  resource.hpp  loop.hpp  handle.hpp  stream.hpp  tcp.hpp

代碼我就不都貼出了,說到哪兒貼哪兒的代碼。如果有興緻可以打開源碼對照看看。另外代碼也比較多,我先大概分析下源碼的結構,再說一些細節的和項目基本無關的東西。

 

源碼很好玩

1、保存自己的share_ptr——通過這個問題來通覽一下源碼。

在第一篇給大家介紹uvw用法的時候,不知道大家有沒有註意到(算了,肯定沒註意),我把大概的代碼貼出來給大家看一下:

 1 void listen(uvw::Loop &loop) {
 2     std::shared_ptr<uvw::TcpHandle> tcp = loop.resource<uvw::TcpHandle>();
 3 
 4 。。。。。。
 5 
 6 }
 7 
 8 void conn(uvw::Loop &loop) {
 9     auto tcp = loop.resource<uvw::TcpHandle>();
10 。。。。。。
11 
12 }
13 
14 void g() {
15     auto loop = uvw::Loop::getDefault();
16     listen(*loop);
17     conn(*loop);
18     loop->run();
19     loop = nullptr;
20 }
21 
22 int main() {
23     g();
24 }

這兒listen和conn函數中,都有一個tcp變數,但是這個變數在函數內部,按道理說,按照g()中的順序走下去,這兩個局部變數應該早已經被自動銷毀了,但是為什麼還能再回調到事件處理函數?有的看官可能會猜測,是不是他們都保存在loop中,其實我一開始也這麼認為,畢竟是調用loop的resource方法創建的。那就先看看resource的代碼:

源碼1  loop.hpp  266-270

1     template<typename R, typename... Args>
2     std::enable_if_t<not std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
3     resource(Args&&... args) {
4         return R::create(shared_from_this(), std::forward<Args>(args)...);
5     }

我們把TcpHandle的模板參數帶進去看,哦,看來這邊是調用的TcpHandle::create()這個靜態函數,難道這就證明瞭loop沒有保存TcpHandle? 但是create中明明傳入了shared_from_this()參數,於是不死心,繼續找create的實現,話說這麼多類拐來拐去的,著實找了一陣子,終於找到了:

源碼2  underlying_type.hpp  76-79

1     template<typename... Args>
2     static std::shared_ptr<T> create(Args&&... args) {
3         return std::make_shared<T>(ConstructorAccess{0}, std::forward<Args>(args)...);
4     }

看到這裡,我就懵比了,傳進來的 std::shared_ptr<Loop> 難道是跟 args 一起,被 std::forward 給吃了?好吧,那既然是要創建一個 std::shared_ptr<TcpHandle>,而且還傳入了一堆參數,肯定是有TcpHandle的構造函數的吧。於是我把TcpHandle類的八輩兒祖宗都找了一遍,終於還是在underlying_type.hpp中找到了:

源碼3  underlying_type.hpp  57-59

1     explicit UnderlyingType(ConstructorAccess, std::shared_ptr<Loop> ref) noexcept
2         : pLoop{std::move(ref)}, resource{}
3     {}

話說這個構造函數里也什麼都沒乾,只是把loop保存了一下啊。那上面的問題怎麼解釋。於是我又看了一遍,原來Loop::resource還有一個實現:

源碼4  loop.hpp  248-254

1     template<typename R, typename... Args>
2     std::enable_if_t<std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
3     resource(Args&&... args) {
4         auto ptr = R::create(shared_from_this(), std::forward<Args>(args)...);
5         ptr = ptr->init() ? ptr : nullptr;
6         return ptr;
7     }

這個實現與源碼1 長的特別像,這在下麵會說到。來看看這個實現裡面。果然,多調用了一個init,而這個init貌似是TcpHandler的成員函數,來看看init裡面有什麼東西,

源碼5  tcp.hpp  62-66

1     bool init() {
2         return (tag == FLAGS)
3                 ? initialize(&uv_tcp_init_ex, flags)
4                 : initialize(&uv_tcp_init);
5     }

源碼6  handle.hpp  45-58

 1     template<typename F, typename... Args>
 2     bool initialize(F &&f, Args&&... args) {
 3         if(!this->self()) {
 4             auto err = std::forward<F>(f)(this->parent(), this->get(), std::forward<Args>(args)...);
 5 
 6             if(err) {
 7                 this->publish(ErrorEvent{err});
 8             } else {
 9                 this->leak();
10             }
11         }
12 
13         return this->self();
14     }

這裡面其實就是用 uv_tcp_init 來做了一下初始化,可以看到第4行,this->parent就是loop指針,this->get就是uv_tcp_t,我就不貼代碼了,大家從源碼里翻看一下。這裡如果初始化成功是肯定會調用leak的,繼續往下看

源碼7  resource.hpp  27-29

1     void leak() noexcept {
2         sPtr = this->shared_from_this();
3     }

這裡就一個作用,把 this->shared_from_this() 賦給了自己的成員變數。難道這就是問題的關鍵所在?

 

我不服,怎麼可能有這種操作,於是我做了個實驗,代碼如下:

 1 #include <iostream>
 2 #include <thread>
 3 #include <mutex>
 4 #include <condition_variable>
 5 
 6 std::mutex g_mutex;
 7 std::condition_variable g_cond;
 8 
 9 class C : public std::enable_shared_from_this<C> {
10 public:
11     C() {
12         std::cout << "C" << std::endl;
13         msg = "Hello World";
14     }
15 
16     ~C() {
17         std::cout << "~C" << std::endl;
18     }
19 
20     void init() {
21         local = this->shared_from_this();
22     }
23 
24     void thread_fun() {
25         while(true) {
26             std::unique_lock<std::mutex> lk(g_mutex);
27             g_cond.wait(lk);
28             std::cout << msg << std::endl;
29         }
30     }
31 
32     void print() {
33         th = std::thread(&C::thread_fun, this);
34         th.detach();
35     }
36 
37 private:
38     std::shared_ptr<C> local;
39     std::thread th;
40     std::string msg;
41 };
42 
43 void fun() {
44     shared_ptr<C> c = std::make_shared<C>();
45     c->init();
46     c->print();
47 }
48 
49 
50 int main(int argc, char* argv[])
51 {
52     fun();
53     std::cout << "fun finish" << std::endl;
54     std::this_thread::sleep_for(std::chrono::milliseconds(1000));
55 
56     for(int i = 0; i < 4; i++) {
57         g_cond.notify_all();
58         std::this_thread::sleep_for(std::chrono::milliseconds(1000));
59     }
60 
61     return 0;
62 }

這個測試代碼基本是模擬了源碼中的情況,用線程加條件變數來模擬信號的產生,輸出結果如下:

1 C
2 fun finish
3 Hello World
4 Hello World
5 Hello World
6 Hello World

如果把上面代碼的第45行註釋掉,輸出結果:

1 C
2 ~C
3 fun finish
4
5
6
7

註意,下麵4行是列印出來的,對比一下這兩個運行結果,第一種情況在fun結束之前是沒有調用C類析構函數的,直到程式運行結束。而第二種情況在fun結束之前就調用了析構函數。這還不夠,雖然兩種情況中線程一直都在運行,但是第二種沒有列印出“Hello World”,更加說明瞭上面的假設。

然後就是怎麼來解釋這種情況,我把fun函數改造一下:

1 void fun() {
2     shared_ptr<C> c = std::make_shared<C>();
3     std::cout << "use count: " << c.use_count() << std::endl;
4     c->init();
5     std::cout << "use count: " << c.use_count() << std::endl;
6     c->print();
7 }

大家可以試一下,第一次列印,引用計數是1,第二次列印,引用計數是2。當fun結束時,c被銷毀,引用計數-1,還剩下1,保存在類中的local中,所以還不能夠釋放記憶體。或者說,在fun中構造的c,永遠都不會釋放,直到程式結束,程式所用的記憶體會由操作系統自動回收。

可能有些人見過這種用法,但是我確實是第一次碰到。不管各位看官感覺怎麼樣,反正我是覺得作者棒棒噠,簡直就是一個心機boy,KeKe~~

看到這裡,還有一個問題,為什麼作者不把Handle保存在Loop中,而要以這種方式來處理呢?其實我們可以在Loop中聲明一個

1     std::vector<std::shared_ptr<void>> handles;

這樣不就可以保存Handle了,或許作者還有其他的考慮,我們以後再看。

 

2、代碼的結構

其實如果有看官跟著上面的步驟走一下,基本上應該是把這個項目的大部分東西都瞭解了一下,項目的大概的繼承關係也會比較清楚了,其他的其實就是一些對libuv東西的封裝和使用,有興趣把源碼來回翻看一下。我相信對於接觸c++時間較短或者對c++11,14標準比較生疏的會受益匪淺。同時,如果你是libuv的使用者,你可能會從裡面學到一些其他的使用方法。

很多同學會自己看一些項目的源代碼,但是很多人看一半,或者看一丟丟對自己有用的,就放下了。對於我們程式員來說,看質量好的源代碼是非常重要的,我們可以從中瞭解作者的思想,作者解決問題的思路和方法,作者每行代碼的企圖,以及項目的設計和規劃,還有其他好多好多東西,就算再不行,我們也可以借鑒人家的代碼,進行修改,這也是一種學習方式。

說了這麼多廢話,就一個意思,很多東西我寫出來,一是表達不好,二是大家看了也是一頭霧水,所以有興趣的還是看源碼來的徹底。

來看一下這邊代碼結構和繼承關係是怎麼的:

 這是從代碼生成的docxgen文檔中截的,文檔下載鏈接:https://files.cnblogs.com/files/yxfangcs/uvw_html.zip

 

一些C++的東東

1、std::unique_ptr

上次有跟大家提到過一點智能指針的東東,給了一個鏈接回顧一下的。但是有些東西沒說到,今天一起看一下。先看代碼:

源碼8  loop.hpp  184-202

 1     static std::shared_ptr<Loop> getDefault() {
 2         static std::weak_ptr<Loop> ref;
 3         std::shared_ptr<Loop> loop;
 4 
 5         if(ref.expired()) {
 6             auto def = uv_default_loop();
 7 
 8             if(def) {
 9                 auto ptr = std::unique_ptr<uv_loop_t, Deleter>(def, [](uv_loop_t *){});
10                 loop = std::shared_ptr<Loop>{new Loop{std::move(ptr)}};
11             }
12 
13             ref = loop;
14         } else {
15             loop = ref.lock();
16         }
17 
18         return loop;
19     }

且先不看這個函數是幹嘛的,看到第9行。我們正常用std::unique_ptr基本就是這樣的:

1 std::unique_ptr<uv_loop_t> ptr = std::make_unique<uv_loop_t>();

然後我們也知道,unique_ptr要用move來傳遞,我們也知道,這個智能指針會在離開作用域的時候自動釋放。像第9行這樣的用法大家可能就很少看到了,先來看看unique_ptr的原形:

1 template<
2     class T,
3     class Deleter = std::default_delete<T>
4 > class unique_ptr;
5 
6 template <
7     class T,
8     class Deleter
9 > class unique_ptr<T[], Deleter>;

哦,這下就知道了,原來是有這麼個東西存在的,這裡的模板變數T就是我們正常傳入的類型,而Deleter是有一個預設值的,std::default_delete<T> 基本上就類似於delete了,這裡我們也是可以自定義的,像上面用法中的Deleter我們可以在代碼中找到:

源碼9  loop.hpp  143

1     using Deleter = void(*)(uv_loop_t *);

這個using的用法在之前的博客中有寫過。在這個用法中,我們可以自行定義unique_ptr的構造和銷毀的操作,看下麵的例子:

1 std::unique_ptr<std::FILE, decltype(&std::fclose)> fp(std::fopen("demo.txt", "r"), &std::fclose);
3 if(fp)
4   std::cout << (char)std::fgetc(fp.get()) << '\n';

(這段來自:http://en.cppreference.com/w/cpp/memory/unique_ptr 有興趣可以點開看看)

怎麼樣,這樣用是不是特別舒服。在離開fp的作用域後,unique_ptr會自動調用fclose來關閉文件。這裡面有一個decltype,這個東西其實就是來返回參數的類型的,比如上面我不知道fclose的原形是什麼,那麼我可以直接用decltype來返回它的類型。舉個例子:

1 auto fun1 = [](int a){return a;};
2 decltype(fun1) fun2 = fun1;

再看到源碼1的第10行,這兒用{}來初始化,在之前博客中也說到過,叫列表初始化,上面打開文件的例子也可以這樣寫:

1 std::unique_ptr<std::FILE, decltype(&std::fclose)> fp{std::fopen("demo.txt", "r"), &std::fclose};
2 if(fp)
3    std::cout << (char)std::fgetc(fp.get()) << '\n';

也是沒關係的。

 

2、std::enable_if_t

把上面代碼再貼一下,方便看

源碼10  loop.hpp  248-254

1     template<typename R, typename... Args>
2     std::enable_if_t<std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
3     resource(Args&&... args) {
4         auto ptr = R::create(shared_from_this(), std::forward<Args>(args)...);
5         ptr = ptr->init() ? ptr : nullptr;
6         return ptr;
7     }

這邊的enable_if_t的原型是:

1 template<bool B, class T = void>
2 struct enable_if;
3 
4 template< bool B, class T = void >
5 using enable_if_t = typename enable_if<B,T>::type;

enable_if 的主要作用就是當某個 成立時,enable_if可以提供某種類型。但是當 不滿足的時候,enable_if<>::type 就是未定義的,當用到模板相關的場景時,只會實例化失敗,不會編譯錯誤。

對於上面的例子,意思就是,如果R的基類是BaseHandle,那返回的類型就是std::share_ptr<R>,否則返回的類型是未定義的,也就是說resource函數模板會實例化失敗,程式運行錯誤。具體可以看:http://en.cppreference.com/w/cpp/types/enable_if

那如果實例化失敗那程式不就掛了,所以作者又給了下麵的一段實現:

源碼11  loop.hpp  266-270

1 template<typename R, typename... Args>
2     std::enable_if_t<not std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
3     resource(Args&&... args) {
4         return R::create(shared_from_this(), std::forward<Args>(args)...);
5     }

意思就是如果R的基類不是BaseHandle就用這個函數模板,這個函數模板里就沒有源碼10中的對init的調用,可見作者還是考慮的非常詳盡的。

 

下一篇

下一篇就來看一下項目中其他文件中的一些東西,看看有沒什麼好玩的介紹給大家,可能再寫個一兩篇就可以結束了。文中有不當或有可改進之處,希望大家不吝賜教,謝謝。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 記錄英語單詞時,想把英語和中文翻譯分別對齊,有些人寫代碼喜歡把變數按這種方式對齊。在網上沒搜到相關方法,於是自己試著寫代碼去實現,原本以為很簡單,寫的時候才發現有不少問題。先看效果: 普通的 對齊前: 對齊後: 發揮點創意 對齊前: 對齊後: 實現 實現的思路比較簡單,讀取文本文件,按正則分割,找出 ...
  • 1.Ajax原理。 2.面對Hebernate的Session close 時如何處理。 3.瞭解事物嗎,在Spring是如何處理事務的。用了什麼方法。 4.當前有P,C兩張表。C是從表,C表中有一個欄位value,兩表關係是1-N。現求出P表有多少條記錄。(條件:C表的value合計必須大於300 ...
  • 經常使用jQuery插件的attr方法獲取checked屬性值,獲取的值的大小為未定義,此時可以用prop方法獲取其真實值,下麵介紹這兩種方法的區別: 1.通過prop方法獲取checked屬性,獲取的checked返回值為boolean,選中為true,否則為flase 2.如果使用attr方法獲 ...
  • python下實現二叉堆以及堆排序 堆是一種特殊的樹形結構, 堆中的數據存儲滿足一定的堆序。堆排序是一種選擇排序, 其演算法複雜度, 時間複雜度相對於其他的排序演算法都有很大的優勢。 堆分為大頭堆和小頭堆, 正如其名, 大頭堆的第一個元素是最大的, 每個有子結點的父結點, 其數據值都比其子結點的值要大。 ...
  • package com.swift; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import ja... ...
  • 兩個對象進行比較相等,有兩種做法: 1,情況一:當僅僅只是判斷兩個對象是否相等時,只需重寫equals()方法即可。這裡就不用說明 2.情況二:當除了情況一之外,還需知道是那個屬性不同,那麼就需要採用類反射,具體代碼如下: public static void main(String[] args) ...
  • 1.創建Bundle php app/console generate:bundle --namespace=Home/IndexBundle --format=yml 創建bundle會更新app文件夾下的 appKernel.php config/routing.yml ...
  • code: 文件內容:(yesterday) 感想: 思路很簡單就是打開源文件,然後迴圈,把源文件要替換的內容替換再寫入新文件! ...
一周排行
    -Advertisement-
    Play Games
  • 前言 本文介紹一款使用 C# 與 WPF 開發的音頻播放器,其界面簡潔大方,操作體驗流暢。該播放器支持多種音頻格式(如 MP4、WMA、OGG、FLAC 等),並具備標記、實時歌詞顯示等功能。 另外,還支持換膚及多語言(中英文)切換。核心音頻處理採用 FFmpeg 組件,獲得了廣泛認可,目前 Git ...
  • OAuth2.0授權驗證-gitee授權碼模式 本文主要介紹如何筆者自己是如何使用gitee提供的OAuth2.0協議完成授權驗證並登錄到自己的系統,完整模式如圖 1、創建應用 打開gitee個人中心->第三方應用->創建應用 創建應用後在我的應用界面,查看已創建應用的Client ID和Clien ...
  • 解決了這個問題:《winForm下,fastReport.net 從.net framework 升級到.net5遇到的錯誤“Operation is not supported on this platform.”》 本文內容轉載自:https://www.fcnsoft.com/Home/Sho ...
  • 國內文章 WPF 從裸 Win 32 的 WM_Pointer 消息獲取觸摸點繪製筆跡 https://www.cnblogs.com/lindexi/p/18390983 本文將告訴大家如何在 WPF 裡面,接收裸 Win 32 的 WM_Pointer 消息,從消息裡面獲取觸摸點信息,使用觸摸點 ...
  • 前言 給大家推薦一個專為新零售快消行業打造了一套高效的進銷存管理系統。 系統不僅具備強大的庫存管理功能,還集成了高性能的輕量級 POS 解決方案,確保頁面載入速度極快,提供良好的用戶體驗。 項目介紹 Dorisoy.POS 是一款基於 .NET 7 和 Angular 4 開發的新零售快消進銷存管理 ...
  • ABP CLI常用的代碼分享 一、確保環境配置正確 安裝.NET CLI: ABP CLI是基於.NET Core或.NET 5/6/7等更高版本構建的,因此首先需要在你的開發環境中安裝.NET CLI。這可以通過訪問Microsoft官網下載並安裝相應版本的.NET SDK來實現。 安裝ABP ...
  • 問題 問題是這樣的:第三方的webapi,需要先調用登陸介面獲取Cookie,訪問其它介面時攜帶Cookie信息。 但使用HttpClient類調用登陸介面,返回的Headers中沒有找到Cookie信息。 分析 首先,使用Postman測試該登陸介面,正常返回Cookie信息,說明是HttpCli ...
  • 國內文章 關於.NET在中國為什麼工資低的分析 https://www.cnblogs.com/thinkingmore/p/18406244 .NET在中國開發者的薪資偏低,主要因市場需求、技術棧選擇和企業文化等因素所致。歷史上,.NET曾因微軟的閉源策略發展受限,儘管後來推出了跨平臺的.NET ...
  • 在WPF開發應用中,動畫不僅可以引起用戶的註意與興趣,而且還使軟體更加便於使用。前面幾篇文章講解了畫筆(Brush),形狀(Shape),幾何圖形(Geometry),變換(Transform)等相關內容,今天繼續講解動畫相關內容和知識點,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 什麼是委托? 委托可以說是把一個方法代入另一個方法執行,相當於指向函數的指針;事件就相當於保存委托的數組; 1.實例化委托的方式: 方式1:通過new創建實例: public delegate void ShowDelegate(); 或者 public delegate string ShowDe ...