第三章 WebKit智能指针详解

说到智能指针,网上相关资料数不胜数,这里我就我自己的理解给大家分享一下。

1.什么是智能指针

我们在编写c++程序的时候都知道所使用的对象都有着严格定义的生命周期:

      全局对象在程序启动时分配内存,在程序结束时销毁;

      局部对象在进入其定义所在的程序块时被创建,在离开块时被销毁;

      局部static对象在第一次使用前分配,在程序结束时销毁;

      动态对象只有当显示的被释放时,方能被销毁;

动态对象的正确释放被证明是编程中极其容易出错的地方:

常见错误一:忘记delete,导致内存泄露;
常见错误二:野指针,对象已经被释放,这里注意,此时的指针成为了悬垂指针,即指向曾经存在的对象,但该对象已经不再存在。结果未定义,而且难以检测)这时候我们再次使用,会产生使用非法内存的指针;
常见错误三:重复delete;

由于WebKit大量使用动态对象,所以类似这样的错误肯定会有很多,为了更安全的使用动态对象,WebKit使用智能指针来管理动态内存,当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。


2.智能指针实现原理

智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count);

智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针;

每次创建类的新对象时,初始化指针并将引用计数置为1;

当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;

对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数,并增加右操作数所指对象的引用计数;

调用析构函数时,构造函数减少引用计数

如果引用计数减至0,则删除基础对象


3.Webkit智能指针

官方文档(2015.4.27):

http://www.webkit.org/coding/RefPtr.html

WebKit智能指针历史:

最早,很多对象采用引用计数(继承自模板类RefCounted),依靠手动调用其ref()或者deref()的方式来实现

到了2005年,发现越来越多的内存泄露问题,其原因就是ref和deref函数调用不匹配导致。于是WebKit采用智能指针来解决此类问题

但是早期的试验表明智能指针会进行额外的引用计数处理而影响性能。因此我们寻找一种方式来让我们使用智能指针同时避免引用计数跳变(churn)

Maciej Stachowiak设计了一组类模板,RefPtr和PassRefPtr,实现这一模式来解决WebKit中恼人的引用计数问题;而C++11标准中增加的std::move操作可以更高效的来解决,所以PassRefPtr逐渐被废弃

到了2013年,发现针对智能指针和原始指针的使用过程中,判空操作激增,但其实很多都是没有必要的;WebKit开始更多的使用引用(Ref)来代替指针(RefPtr)

WebKit智能指针实现:

WebKit智能指针由类族RefPtr来实现,其核心有如下三个类:RefCounted、RefPtr、Ref

其中RefCounted提供了引用计数器,RefPtr、Ref提供了自动管理引用计数器的功能

RefCounted源码在RefCounted.h中,这个文件里定义了两个类:非模板类RefCountedBase和模板类RefCounted

定义了成员变量:int m_refCount

函数:ref()
函数:deref()
函数:derefBase()

前面的ref()和deref()就是RefCounted的核心功能了,ref时引用计数加1,deref时引用计数减1,减到0就将自己销毁

使用时需要继承自RefCounted,但是这里不同于一般的继承,举例:

但是仅仅使用RefCounted类还无法称之为智能指针,RefCounted使用方法繁琐

每次操作对象都要做ref()或者deref()操作

为了简化RefCounted的使用方法,RefPtr诞生了,其源代码在RefPtr.h中,在这个文件中定义了一个模板类RefPtr,RefPtr才能算一个简单的智能指针

看上面的代码就能很清楚的知道,当把一个对象赋值给RefPtr包装过的对象后,它会先被赋值的对象ref,然后再给自己原来的对象deref,这实际上就是上例中setTitle的过程,所以改写后就极大简洁了代码

修改版

这样修改虽然简洁了代码,但没有简洁代码实际的执行过程。此段代码还是会频繁的使用ref和deref,这样就会导致引用计数跳变的问题,比如:

开始引用计数为1

setTitle将untitledTitle赋值给document的成员变量,引用计数增加为2

此程序块返回,untitledTitle变量销毁,引用计数减少到1

引用计数跳变在函数参数和返回值都涉及的情况下更严重

终极解决方案:

在本例中,引用计数始终为1,另外,WTF::move主要是封装了std::move(此函数在赋值操作中直接取右值,中间不会涉及到引用计数加减),并添加了错误处理。另外还可以结合PassRefPtr处理这种情况,具体百度有相关资料,RefPtr和PassRefPtr,讲得还是非常容易理解的。

Ref源代码在Ref.h中,在这个文件中定义了一个模板类Ref,Ref很像RefPtr,但是Ref是一个引用,而RefPtr是一个指针,Ref是一个智能引用,所以其值不可能为null

智能指针相关函数功能:

get()

可以通过智能指针RefPtr的get函数获取到原始指针

同样,可以通过智能引用Ref的get函数获取到原始引用,也可以通过智能引用Ref的ptr函数获取到原始指针

leafRef()

此函数把管理的指针转移给接受者,不涉及到引用计数的操作

adoptRef()

原始指针转换为智能指针

      一个继承自RefCounted模板类的对象在被创建时,需要保证引用计数值为1。最好的办法是将创建的对象放到Ref中,以免操作完成后忘记调用此对象的deref()函数,这意味着只要调用此类的new操作后要立即调用adoptRef函数

在WebKit中创建对象时,采用create函数替代直接new操作


4.WebKit智能指针使用原则

针对局部变量:

      如果生命周期和所有者是确认的,则允许使用原始引用或指针

      如果代码需要维持对该对象的引用并确定它的生命周期,则应该使用Ref,如果其值可能为null,则使用RefPtr

针对类的成员变量:

      如果生命周期和所有者是确认的,则允许使用原始引用或指针

      如果这个类需要维持对该对象的引用并确定它的生命周期,则应该使用Ref或者RefPtr

针对函数的形参:

      如果该函数不需要维持对该对象的引用,则函数参数允许使用原始引用或指针

      如果该函数需要维持对该对象的引用,则函数参数应该是Ref&&或者RefPtr&&。这种情况常见于很多setter函数

针对函数的返回值:

      如果返回值是一个对象,并且所有者并未转移,则返回值类型应该是原始引用或者指针。这种情况常见于很多getter函数

      如果返回值是一个新创建的对象,或者其所有者因为某些原因需要被转移,则返回值类型应该是Ref或者RefPtr

新对象:

      一个新创建出来的对象应该立即转化为Ref引用,以便智能指针能够自动的对所有引用计数

      针对继承自RefCounted类的对象,上面的过程需要用adoptRef函数来转化

      好的使用智能指针的习惯是将类的构造函数定义为私有函数,并且定义一个公共的create函数用来创建类的对象并返回一个Ref引用


PassRefPtr、RefPtr与Raw Ptr转换图
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,451评论 25 707
  • 引言:由于未来需要深入android底层进行系统级别的开发,所以最近在看老罗的《Android系统源代码情景分析》...
    拿破轮阅读 2,192评论 0 9
  • 指针 在传统的C++编程中,指针的使用一直是一把双刃剑。指针赋予了我们直接操作硬件地址的能力,但同时也带来了诸多问...
    passerbywhu阅读 2,833评论 0 2
  • 前天下午偶然翻阅手机,微信服务通知里显示“微信邀请你使用公众号原创保护功能”。记得我发第一篇公众号文章的时候,就有...
    桃小妖声声慢阅读 365评论 0 0