说到智能指针,网上相关资料数不胜数,这里我就我自己的理解给大家分享一下。
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转换图
网友评论