MyException - 我的异常网
当前位置:我的异常网» C++ » UVW源码纵谈(三)

UVW源码纵谈(三)

www.MyException.Cn  网友分享于:2013-10-08  浏览:0次
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 的主要作用就是当某个 成立时,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的调用,可见作者还是考虑的非常详尽的。

 

下一篇

下一篇就来看一下项目中其他文件中的一些东西,看看有没什么好玩的介绍给大家,可能再写个一两篇就可以结束了。文中有不当或有可改进之处,希望大家不吝赐教,谢谢。

文章评论

那些争议最大的编程观点
那些争议最大的编程观点
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
为什么程序员都是夜猫子
为什么程序员都是夜猫子
10个调试和排错的小建议
10个调试和排错的小建议
旅行,写作,编程
旅行,写作,编程
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
如何区分一个程序员是“老手“还是“新手“?
如何区分一个程序员是“老手“还是“新手“?
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
中美印日四国程序员比较
中美印日四国程序员比较
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
10个帮程序员减压放松的网站
10个帮程序员减压放松的网站
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
程序员的鄙视链
程序员的鄙视链
程序员和编码员之间的区别
程序员和编码员之间的区别
总结2014中国互联网十大段子
总结2014中国互联网十大段子
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
我是如何打败拖延症的
我是如何打败拖延症的
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
Web开发者需具备的8个好习惯
Web开发者需具备的8个好习惯
我的丈夫是个程序员
我的丈夫是个程序员
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
Java程序员必看电影
Java程序员必看电影
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
程序员的一天:一寸光阴一寸金
程序员的一天:一寸光阴一寸金
代码女神横空出世
代码女神横空出世
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
Java 与 .NET 的平台发展之争
Java 与 .NET 的平台发展之争
漫画:程序员的工作
漫画:程序员的工作
老程序员的下场
老程序员的下场
编程语言是女人
编程语言是女人
程序员都该阅读的书
程序员都该阅读的书
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员必看的十大电影
程序员必看的十大电影
鲜为人知的编程真相
鲜为人知的编程真相
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
程序员应该关注的一些事儿
程序员应该关注的一些事儿
每天工作4小时的程序员
每天工作4小时的程序员
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有