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 finish4 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的调用,可见作者还是考虑的非常详尽的。

下一篇

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

时间: 2024-10-05 03:41:13

UVW源码漫谈(三)的相关文章

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

这两天天气凉了,苏州这边连续好几天都是淅淅沥沥的下着小雨,今天天气还稍微好点.前两天早上起来突然就感冒了,当天就用了一卷纸,好在年轻扛得住,第二天就跟没事人似的.在这里提醒大家一下,天气凉了,睡凉席的可以收起来了,体质不太好的,也要适当加点衣服. 本来是想接着看源码的,早上起来又把Emitter鼓捣了一下,跟大家说说. emitter.hpp是可以从源码中剥离出来的,只要去除里面的libuv的东西就行了.Emitter其实就是实现的即时回调,没有异步事件处理的功能.但是我们有时候是需要用并发来提

Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe

Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块就调用对应的probe函数,也就是nouveau_drm_probe. // /drivers/gpu/drm/nouveau/nouveau_drm.c 281 static int nouveau_drm_probe(struct pci_dev *pdev, 282 const struct

[Android]Fragment源码分析(三) 事务

Fragment管理中,不得不谈到的就是它的事务管理,它的事务管理写的非常的出彩.我们先引入一个简单常用的Fragment事务管理代码片段: FragmentTransaction ft = this.getSupportFragmentManager().beginTransaction(); ft.add(R.id.fragmentContainer, fragment, "tag"); ft.addToBackStack("<span style="fo

baksmali和smali源码分析(三)

baksmali 的源码分析 在baksmali进行源码分析之前,需要读者掌握一条主线,因为本身笔者只是由于项目需要用到这套源码,在工作之余的时间里面来进行学习也没有时间和精力熟读源码的每个文件每个方法,但是依据这条主线,至少能够猜出并且猜对baksmali里面的源码的文件大概的作用是什么,这样在修改问题和移植的时候才能做到游刃有余. 这条主线是,baksmali其实只是利用了dexlib2提供的接口,将dex文件读入到一块内存中,这块内存或者说数据结构开辟的大小是跟输入的dex文件相关的,而这

横屏小游戏--萝莉快跑源码分析三

主角出场: 初始化主角 hero = new GameObjHero(); hero->setScale(0.5); hero->setPosition(ccp(100,160)); hero->setVisible(false); addChild(hero,1); 进入GameObjHero类ccp文件 创建主角及动作 this->setContentSize(CCSizeMake(85,90)); //接收触摸事件 CCDirector* pDirector = CCDire

boost.asio源码剖析(三) ---- 流程分析

* 常见流程分析之一(Tcp异步连接) 我们用一个简单的demo分析Tcp异步连接的流程: 1 #include <iostream> 2 #include <boost/asio.hpp> 3 4 // 异步连接回调函数 5 void on_connect(boost::system::error_code ec) 6 { 7 if (ec) // 连接失败, 输出错误码 8 std::cout << "async connect error:"

哇!板球 源码分析三

守门员出场 守门员出场,每个守门员是从屏幕的右侧中间的位置随机方向向左侧移动 FielderSprite* fielderSprite1 = FielderSprite::create("pic/fielder.png"); //守门员精灵初始位置为右侧中间位置 fielderSprite1->setPosition(ccp(GOALKEEPER_X, GOALKEEPER_Y)); fielderSprite1->setAnchorPoint(ccp(0.5, 0.5))

执行LS源码的三种方法

方法一:使用 eval() eval()函数常用来计算表达式,将表达式转换成一个变量名或者对象名,然后使用它访问变量或者对象. LS中的数据都是以字符串的形式存储.当获取到字符串 data时(json数据转换成的字符串), storage = eval("("+data+")");//这样得到的数据就是json串. 备注:在json转换成本地存储的时候,需要将json串转换一下:JSON.stringify(data),这样得到的就是json串.. 方法二:使用ne

ABP源码分析三十三:ABP.Web

ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事一,在Application_Start完成AbpBootstrapper的初始化.整个ABP系统的初始化就是通过AbpBootstrapper完成初始化的.二,在Application_BeginRequest设置根据request或cookie中的Culture信息,完成当前工作线程的CurrentCu