UVW源码漫谈(三)
咱们继续看uvw的源码,这次看的东西比较多,去除底层的一些东西,很多代码都是连贯的,耦合度也比较高了。主要包括下面几个文件的代码:
underlying_type.hpp resource.hpp loop.hpp handle.hpp stream.hpp tcp.hpp
代码我就不都贴出了,说到哪儿贴哪儿的代码。如果有兴致可以打开源码对照看看。另外代码也比较多,我先大概分析下源码的结构,再说一些细节的和项目基本无关的东西。
源码很好玩
1、保存自己的share_ptr——通过这个问题来通览一下源码。
在第一篇给大家介绍uvw用法的时候,不知道大家有没有注意到(算了,肯定没注意),我把大概的代码贴出来给大家看一下:
void listen(uvw::Loop &loop) {
std::shared_ptr<uvw::TcpHandle> tcp = loop.resource<uvw::TcpHandle>(); 。。。。。。 } void conn(uvw::Loop &loop) {
auto tcp = loop.resource<uvw::TcpHandle>();
。。。。。。 } void g() {
auto loop = uvw::Loop::getDefault();
listen(*loop);
conn(*loop);
loop->run();
loop = nullptr;
} int main() {
g();
}
这儿listen和conn函数中,都有一个tcp变量,但是这个变量在函数内部,按道理说,按照g()中的顺序走下去,这两个局部变量应该早已经被自动销毁了,但是为什么还能再回调到事件处理函数?有的看官可能会猜测,是不是他们都保存在loop中,其实我一开始也这么认为,毕竟是调用loop的resource方法创建的。那就先看看resource的代码:
源码1 loop.hpp 266-270
template<typename R, typename... Args>
std::enable_if_t<not std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
return R::create(shared_from_this(), std::forward<Args>(args)...);
}
我们把TcpHandle的模板参数带进去看,哦,看来这边是调用的TcpHandle::create()这个静态函数,难道这就证明了loop没有保存TcpHandle? 但是create中明明传入了shared_from_this()参数,于是不死心,继续找create的实现,话说这么多类拐来拐去的,着实找了一阵子,终于找到了:
源码2 underlying_type.hpp 76-79
template<typename... Args>
static std::shared_ptr<T> create(Args&&... args) {
return std::make_shared<T>(ConstructorAccess{}, std::forward<Args>(args)...);
}
看到这里,我就懵比了,传进来的 std::shared_ptr<Loop> 难道是跟 args 一起,被 std::forward 给吃了?好吧,那既然是要创建一个 std::shared_ptr<TcpHandle>,而且还传入了一堆参数,肯定是有TcpHandle的构造函数的吧。于是我把TcpHandle类的八辈儿祖宗都找了一遍,终于还是在underlying_type.hpp中找到了:
源码3 underlying_type.hpp 57-59
explicit UnderlyingType(ConstructorAccess, std::shared_ptr<Loop> ref) noexcept
: pLoop{std::move(ref)}, resource{}
{}
话说这个构造函数里也什么都没干,只是把loop保存了一下啊。那上面的问题怎么解释。于是我又看了一遍,原来Loop::resource还有一个实现:
源码4 loop.hpp 248-254
template<typename R, typename... Args>
std::enable_if_t<std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
auto ptr = R::create(shared_from_this(), std::forward<Args>(args)...);
ptr = ptr->init() ? ptr : nullptr;
return ptr;
}
这个实现与源码1 长的特别像,这在下面会说到。来看看这个实现里面。果然,多调用了一个init,而这个init貌似是TcpHandler的成员函数,来看看init里面有什么东西,
源码5 tcp.hpp 62-66
bool init() {
return (tag == FLAGS)
? initialize(&uv_tcp_init_ex, flags)
: initialize(&uv_tcp_init);
}
源码6 handle.hpp 45-58
template<typename F, typename... Args>
bool initialize(F &&f, Args&&... args) {
if(!this->self()) {
auto err = std::forward<F>(f)(this->parent(), this->get(), std::forward<Args>(args)...); if(err) {
this->publish(ErrorEvent{err});
} else {
this->leak();
}
} return this->self();
}
这里面其实就是用 uv_tcp_init 来做了一下初始化,可以看到第4行,this->parent就是loop指针,this->get就是uv_tcp_t,我就不贴代码了,大家从源码里翻看一下。这里如果初始化成功是肯定会调用leak的,继续往下看
源码7 resource.hpp 27-29
void leak() noexcept {
sPtr = this->shared_from_this();
}
这里就一个作用,把 this->shared_from_this() 赋给了自己的成员变量。难道这就是问题的关键所在?
我不服,怎么可能有这种操作,于是我做了个实验,代码如下:
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable> std::mutex g_mutex;
std::condition_variable g_cond; class C : public std::enable_shared_from_this<C> {
public:
C() {
std::cout << "C" << std::endl;
msg = "Hello World";
} ~C() {
std::cout << "~C" << std::endl;
} void init() {
local = this->shared_from_this();
} void thread_fun() {
while(true) {
std::unique_lock<std::mutex> lk(g_mutex);
g_cond.wait(lk);
std::cout << msg << std::endl;
}
} void print() {
th = std::thread(&C::thread_fun, this);
th.detach();
} private:
std::shared_ptr<C> local;
std::thread th;
std::string msg;
}; void fun() {
shared_ptr<C> c = std::make_shared<C>();
c->init();
c->print();
} int main(int argc, char* argv[])
{
fun();
std::cout << "fun finish" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds()); for(int i = ; i < ; i++) {
g_cond.notify_all();
std::this_thread::sleep_for(std::chrono::milliseconds());
} return ;
}
这个测试代码基本是模拟了源码中的情况,用线程加条件变量来模拟信号的产生,输出结果如下:
C
fun finish
Hello World
Hello World
Hello World
Hello World
如果把上面代码的第45行注释掉,输出结果:
C
~C
fun finish
4
5
6
7
注意,下面4行是打印出来的,对比一下这两个运行结果,第一种情况在fun结束之前是没有调用C类析构函数的,直到程序运行结束。而第二种情况在fun结束之前就调用了析构函数。这还不够,虽然两种情况中线程一直都在运行,但是第二种没有打印出“Hello World”,更加说明了上面的假设。
然后就是怎么来解释这种情况,我把fun函数改造一下:
void fun() {
shared_ptr<C> c = std::make_shared<C>();
std::cout << "use count: " << c.use_count() << std::endl;
c->init();
std::cout << "use count: " << c.use_count() << std::endl;
c->print();
}
大家可以试一下,第一次打印,引用计数是1,第二次打印,引用计数是2。当fun结束时,c被销毁,引用计数-1,还剩下1,保存在类中的local中,所以还不能够释放内存。或者说,在fun中构造的c,永远都不会释放,直到程序结束,程序所用的内存会由操作系统自动回收。
可能有些人见过这种用法,但是我确实是第一次碰到。不管各位看官感觉怎么样,反正我是觉得作者棒棒哒,简直就是一个心机boy,KeKe~~
看到这里,还有一个问题,为什么作者不把Handle保存在Loop中,而要以这种方式来处理呢?其实我们可以在Loop中声明一个
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
template<typename R, typename... Args>
std::enable_if_t<std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
auto ptr = R::create(shared_from_this(), std::forward<Args>(args)...);
ptr = ptr->init() ? ptr : nullptr;
return ptr;
}
这边的enable_if_t的原型是:
template<bool B, class T = void>
struct enable_if; template< bool B, class T = void >
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
template<typename R, typename... Args>
std::enable_if_t<not std::is_base_of<BaseHandle, R>::value, std::shared_ptr<R>>
resource(Args&&... args) {
return R::create(shared_from_this(), std::forward<Args>(args)...);
}
意思就是如果R的基类不是BaseHandle就用这个函数模板,这个函数模板里就没有源码10中的对init的调用,可见作者还是考虑的非常详尽的。
下一篇
下一篇就来看一下项目中其他文件中的一些东西,看看有没什么好玩的介绍给大家,可能再写个一两篇就可以结束了。文中有不当或有可改进之处,希望大家不吝赐教,谢谢。
UVW源码漫谈(三)的更多相关文章
- UVW源码漫谈(番外篇)—— Emitter
这两天天气凉了,苏州这边连续好几天都是淅淅沥沥的下着小雨,今天天气还稍微好点.前两天早上起来突然就感冒了,当天就用了一卷纸,好在年轻扛得住,第二天就跟没事人似的.在这里提醒大家一下,天气凉了,睡凉席的 ...
- UVW源码漫谈(四)
十一假期后就有点懒散,好长时间都没想起来写东西了.另外最近在打LOL的S赛.接触LOL时间不长,虽然平时玩的比较少,水平也相当菜,但是像这种大型的赛事有时间还是不会错过的.主要能够感受到选手们对竞技的 ...
- UVW源码漫谈(二)
前一篇发布出来之后,我看着阅读量还是挺多的,就是评论和给意见的一个都没有,或许各位看官就跟我一样,看帖子从不回复,只管看就行了.毕竟大家都有公务在身,没太多时间,可以理解.不过没关系,我是不是可以直接 ...
- tomcat源码分析(三)一次http请求的旅行-从Socket说起
p { margin-bottom: 0.25cm; line-height: 120% } tomcat源码分析(三)一次http请求的旅行 在http请求旅行之前,我们先来准备下我们所需要的工具. ...
- 25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment
25 BasicUsageEnvironment0基本使用环境基类——Live555源码阅读(三)UsageEnvironment 25 BasicUsageEnvironment0基本使用环境基类— ...
- 26 BasicUsageEnvironment基本使用环境——Live555源码阅读(三)UsageEnvironment
26 BasicUsageEnvironment基本使用环境--Live555源码阅读(三)UsageEnvironment 26 BasicUsageEnvironment基本使用环境--Live5 ...
- 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment 24 UsageEnvironment使用环境抽象基类——Live555源码阅读 ...
- Celery 源码解析三: Task 对象的实现
Task 的实现在 Celery 中你会发现有两处,一处位于 celery/app/task.py,这是第一个:第二个位于 celery/task/base.py 中,这是第二个.他们之间是有关系的, ...
- jdk源码剖析三:锁Synchronized
一.Synchronized作用 (1)确保线程互斥的访问同步代码 (2)保证共享变量的修改能够及时可见 (3)有效解决重排序问题.(Synchronized同步中的代码JVM不会轻易优化重排序) 二 ...
随机推荐
- hdu_4497GCD and LCM(合数分解)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4497 GCD and LCM Time Limit: 2000/1000 MS (Java/Other ...
- Treap(树堆)
treap是排序二叉树的一种改进,因为排序二叉树有可能会造成链状结构的时候复杂度变成O(n^2)所以通过随机一个优先级的方法来维持每次让优先级最大的作为树根,然后形成一个满足: A. 节点中的key满 ...
- Linux编译安装Mariadb数据库
一.安装cmake cd /usr/local/src tar zxvf cmake-2.8.12.1.tar.gz cd cmake-2.8.12.1 ./configure 注意报错需要安装gcc ...
- event.target与event.srcElement
target 事件属性可返回事件的目标节点(触发该事件的节点),如生成事件的元素.文档或窗口. 在标准浏览器下我们一般使用event.target就能解决,然而低版本IE浏览器总是会出些幺蛾子,这时候 ...
- Oracle_基本函数查询综合
Oracle_基本函数查询综合 --[1]查询出每各月倒数第三天受雇的所有员工 select; --[2]找出早于30年前受雇的员工 select>; select; select; ...
- linux 树型显示文件 tree ls tree 命令
原创 2016年07月27日 09:50:19 yum install tree tree www │?? │?? │?? └── xml.test │?? │?? └── valgrind.su ...
- HTTP 405 错误 – 方法不被允许 (Method not allowed)【转载】
介绍 HTTP 协议定义一些方法,以指明为获取客户端(如您的浏览器或我们的 CheckUpDown 机器人)所指定的具体网址资源而需要在 Web 服务器上执行的动作.则这些方法如下: OPTIONS( ...
- thinkphp5z
解决验证类下找不到指定的类,就在D:\phpStudy\www\vuethink\php\application\admin\validate\service.php,缺少验证类文件service.p ...
- 独立服务器 云主机、VPS以及虚拟主机三者之间的区别是什么?哪个更好?
https://www.zhihu.com/question/21442353#answer-2442764 云主机(如 EC2,[1] )和 VPS (如 Linode,[2])都是完整的操作系统( ...
- [SinGuLaRiTy] NOIP互测模拟赛
[SinGuLaRiTy-1045] Copyright (c) SinGuLaRiTy 2017. All Rights Reserved. 源文件名 输入输出文件 时间限制 内存限制 淘气的cch ...