UVW源碼漫談(三)
咱們繼續看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 的主要作用就是當某個 B 成立時,enable_if可以提供某種類型。但是當 B 不滿足的時候,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的調用,可見作者還是考慮的非常詳盡的。
下一篇
下一篇就來看一下項目中其他文件中的一些東西,看看有沒什麽好玩的介紹給大家,可能再寫個一兩篇就可以結束了。文中有不當或有可改進之處,希望大家不吝賜教,謝謝。
UVW源碼漫談(三)