[WebKit]RefPtr和PassRefPtr基础

原帖地址 : http://blog.csdn.net/cnwarden/article/details/4628049


历史:

     在WebKit中,许多对象采用了引用计数。这种模式是通过类的ref,deref成员函数来递增和递减对象的引用记数。调用一次ref必须调用一次deref。当对象的引用记数为0的时候,对象就被删除。WebKit中许多类创建的新对象引用记数都为0,这被称作是浮动状态(Floating State)。在浮动状态的对象必须调用ref,在删除之前必须调用deref。WebCore中许多类通过继承RefCounted模版类来实现这种模式。
在2005年的时候,我们发现存在很多内存泄漏的问题,特别实在WebCore编辑器代码中,这主要是由没有正确的使用ref和deref调用,还有就是创建的对象没有调用ref,依然是浮动状态。
     我们决定我们使用智能指针来解决这个问题,然而,一些前期的实验表明,智能指针导致引用记数的其他操纵影响性能。例如,一个函数使用智能指针来传递参数,函数返回时也使用这个智能指针作为返回值,仅仅在一个对象从一个智能指针移动到另外一个时,传递参数和返回函数值时就递增和递减引用记数2-4次。因此,我们寻求一种能够让我们使用智能指针又避免使用这种引用记数的性能流失的方法。
这种解决方案的灵感来源于C++的标准模版类auto_ptr。应用这种模式的对象在赋值的时候将传递了所有权。当你把一个auto_ptr传递给另外一个时,传递者变为0。
      Maciej Stachowiak设计了一对模版类,RefPtr和PassRefPtr,它为WebCore的引用记数实现了这种模式。
原始指针:

      在讨论如RefPtr模版类这类智能指针时,我们使用原始指针来构建,下面是使用原始指针写的规范的Setter函数。

// example, not preferred style
class Document {
    [...]
    Title* m_title;
}
Document::Document(): m_title(0)
{
}
Document::~Document()
{
    if (m_title)
        m_title->deref();
}
void Document::setTitle(Title* t)
{
    if (t)
        t->ref();
    if (m_title)
        m_title->deref();
    m_title = t;
}

RefPtr
RefPtr是一种简单的智能指针,主要是通过在传入值时调用ref,传出时调用deref。RefPtr工作在有ref和deref成员函数的对象上。下面是一个例子:

// example, not preferred style
 
class Document {
    [...]
    RefPtr<Title> m_title;
}
void Document::setTitle(Title* t)
{
    m_title = t;
}


单独使用RefPtr会导致引用记数的流失,例如下面的例子:

// example, not preferred style
 
RefPtr<Node> createSpecialNode()
{
    RefPtr<Node> a = new Node;
    a->setSpecial(true);
    return a;
}
RefPtr<Node> b = createSpecialNode();

我们假设node对象的引用计数从0开始,当它赋值给a的时候,引用计数增加了1(引用operator=被RefPtr重载了),从函数返回的时候,因为a实际上只是一个临时变量,编译器会把它复制给一个临时变量作为返回值,此时发生了赋值操作,导致引用计数变为2,同时由于a是临时变量,所以在a被销毁的时候,析构函数被调用,导致引用计数变为1。作为返回值的临时变量把值赋值给变量b,此时引用计数又会增加为2,在临时变量销毁的时候,引用计数又变为1。(如果编译器进行了返回值优化的话,会减少一次引用计数的增加减少过程)如果函数的参数和返回值都涉及了智能指针的使用,那么引用计数导致的overhead会更多。

当涉及到函数参数和返回值时,引用记数的流失的代价比较大,解决方法就是PassRefPtr。


PassRefPtr
PassRefPtr除过有一点区别其他都和RefPtr类似,当传递一个PassRefPtr,或者把PassRefPtr赋值到RefPtr或者另一PassRefPtr时,原始的指针值设置为0;操作没有做任何对引用记数的更改。下面看一个新的例子:


// example, not preferred style
PassRefPtr<Node> createSpecialNode()
{
    PassRefPtr<Node> a = new Node;
    a->setSpecial(true);
    return a;
}
RefPtr<Node> b = createSpecialNode();

假设Node对象起始引用计数为0,当它赋值给a的时候,引用计数增加为1。在函数返回的时候,变量a赋值给返回结果临时变量的时候,a中的引用被设置为0,在返回结果临时变量赋值给变量b的时候,b中的引用被设置为0。

注:PassRefPtr通过在赋值的时候,传递指针而不是增加减少引用计数的方式,降低了引用计数泛滥导致的性能降低问题。

然后,当我们开始使用PassRefPtr编码时,Safari团队发现当被赋值到另一个变量时,这种很容易导致错误。

// example, not preferred style
 
static RefPtr<Ring> g_oneRingToRuleThemAll;
void finish(PassRefPtr<Ring> ring)
{
    g_oneRingToRuleThemAll = ring;
    ...
    ring->wear();
}

在wear被调用时,ring已经是空指针了,为了避免这种情况,建议PassRefPtr只在作为函数参数,返回值和拷贝参数到RefPtr局部变量时使用。

static RefPtr<Ring> g_oneRingToRuleThemAll;
void finish(PassRefPtr<Ring> prpRing)
{
    RefPtr<Ring> ring = prpRing;
    g_oneRingToRuleThemAll = ring;
    ...
    ring->wear();
}

混合使用RefPtr和PassRefPtr


我们建议,在作为参数进行传递和函数返回值的情况下使用PassRefPtr,其他情况下使用RefPtr,但是也有很多时候,你会希望像PassRefPtr那样,转移RefPtr的所有权。RefPtr有一个名为release的成员函数实现着这功能。它设置RefPtr的值为0,并且构建新的PassRefPtr而不改变引用计数。

// example, assuming new Node starts with reference count 0
 
PassRefPtr<Node> createSpecialNode()
{
    RefPtr<Node> a = new Node;
    a->setCreated(true);
    return a.release();
}
RefPtr<Node> b = createSpecialNode();

这种方式保持了PassRefPtr的有效性,同时也降低了导致问题的可能性。


与原始指针混合使用


RefPtr使用get方法来获得一个原始指针

printNode(stderr, a.get());

然而,这些操作可以不使用get调用,而通过RefPtr和PassRefPtr直接完成。

RefPtr<Node> a = createSpecialNode();
Node* b = getOrdinaryNode();
// the * operator
*a = value;
// the -> operator
a->clear();
// null check in an if statement
if (a)
    log("not empty");
// the ! operator
if (!a)
    log("empty");
// the == and != operators, mixing with raw pointers
if (a == b)
    log("equal");
if (a != b)
    log("not equal");
// some type casts
RefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);

注:RefPtr对多种操作符都进行了重载

一般来说,RefPtr和PassRefPtr践行了一个简单的规则:他们总是平衡ref和deref调用,来保证程序员不会丢失对deref的调用。但是对于我们已经有了一个原始指针,并且有了引用计数,希望能够转移它的所有权的情况,应该使用adoptRef函数。

RefPtr node =adoptRef(rawNodePointer);

反过来,如果想把RefPtr传递给一个原始指针,而不改变引用计数,那么就要使用PassRefPtr的leakRef函数

// warning, results in a pointer that must get an explicit deref
RefPtr<Node> node = createSpecialNode();
Node* rawNodePointer = node.release().leakRef();、


RefPtr和new objects

在这里的例子中,我们谈论的对象都是引用计数为0,但是为了效率和简化,RefCounted类根本不使用引用计数0。对象在创建的时候就使用引用计数1.最好的编程实践是在new以后,马上把它们放进RefPtr,这样可以防止程序员在用完这些对象的时候忘记调用deref,让RefPtr帮助我们管理这些对象。也就是说,任何人调用new创建一个对象后,要马上调用adoptRef(该函数会返回一个PassRefPtr)。在WebCore中我们使用命名为create的static函数,而不是直接使用new

PassRefPtr Node::create()
{
    return adoptRef(new Node);
}
RefPtr e = Node::create();

从adoptRef和PassRefPtr的实现,我们可以看到它们是很高效的。对象的起始引用计数是1,而且在创建过程中没有任何对引用计数的操作

// preferred style

PassRefPtr createSpecialNode()

{
   RefPtr a = Node::create();
   a->setCreated(true);
    return a.release();
}
RefPtr b = createSpecialNode();

Node对象在Node::create中,被adoptRef放进PassRefPtr中,然后通过RefPtr的重载过的=操作符赋值给RefPtr对象a,最后在函数返回的时候,RefPtr的release函数返回PassRefPtr对象,然后又通过赋值操作赋值给RefPtr对象b,这期间没有对引用计数发生过任何操作。

RefCounted类实现了一个运行期的检查,如果我们再创建一个对象之后,没有调用adoptRef,而调用ref或者deref将会发生assertion。

使用指南

在WebKit代码中使用RefPtr和PassRefPtr应遵守如下的使用指南:

本地变量

如果所有权和生命期可以保证是在局部的,那么本地变量可以使用原始指针

如果代码需要保持住所有权或者保证生命期,那么应该使用RefPtr本地变量

永远不要使用PassRefPtr作为本地变量

成员变量

如果能够保障成员变量的所有权和生命期,成员变量可以使用原始指针

如果类需要获得所有权或者保证生命期,那么成员变量应该使用RefPtr

成员变量永远不要使用PassRefPtr

函数参数

如果函数不用于获取对象的所有权,那么参数可以使用原始指针

如果函数需要获取对象的所有权,那么参数应该是PassRefPtr。大部分的setter函数都应该如此设计。除非参数非常简单,否则的话,参数应该在函数一开始就传递给RefPtr对象,通常函数参数名称可以以”prp”作为前缀

函数结果

如果函数结果是一个对象,但是该对象的所有权还是属于原来对象,那么这个返回对象应该是一个原始指针,大部分的getter函数都是如此定义的(注:即结果对象是包含在原始对象中的,通过getter只是返回其引用或者指针,但是该返回的对象仍然从属于原始对象)

如果函数结果是一个新生成的对象或者它的所有权因为某种原因需要转移,那么这个结果应该使用PassRefPtr(该对象可以转移所有权)。因为本地变量一般使用RefPtr,所以在函数返回的时候,应该调用RefPtr的release函数返回一个PassRefPtr对象

新对象

新创建的对象在创建后需要马上放入到RefPtr中,这样只能指针就是自动控制引用计数操作

对于RefCounted对象,应该使用adoptRef函数把它放入到PassRefPtr中。

注:WebCore中的例子

static PassRefPtr DocumentLoader::create(constResourceRequest& request, const SubstituteData& data)
{
    return adoptRef(new DocumentLoader(request, data));
}

DocumentLoader继承自RefCounted

最好的创建方式是,使用私有的构造函数,和共有的创建函数,该创建函数返回一个PassRefPtr,(注:采用私有的构造函数,就防止外界使用new来创建新的对象,必须使用类提供的静态创建函数,该函数保证了对象在被创建后马上就被放入到RefPtr中)

这篇文章是关于Webkit中一系列非常基础的类的说明,看明白这篇文档之前还需要了解如下信息:

RefCounted是所有智能指针的基类,它提供了ref和deref操作,用于增减引用计数,RefCounted还有它自己的基类,RefCountedBase。

为了解决引用计数在作为参数和返回值的时候,频繁增减的问题,引入RefPtr和PassRefPtr对象,和一系列使用这些对象的规则

首先需要说明的是RefPtr和PassRefPtr是模板,使用这些模板定义的对象同样也一个对象而不是指针,在这些对象中封装了对引用计数的ref和deref操作。

其次来看一些重要的方法实现,对于我们理解上述文档很有好处,

RefPtr::release(),该函数返回一个PassRefPtr对象,用于传递原始指针的所有权

PassRefPtr RefPtr::release()
{
       PassRefPtr tmp = adoptRef(m_ptr);m_ptr = 0; 
      return tmp;
}
PassRefPtr::leakRef(),该函数返回原始指针

template inline T*PassRefPtr::leakRef() const

{

T* ptr = m_ptr;

m_ptr = 0;

return ptr;

}

adoptRef这个函数非常重要,它返回一个PassRefPtr对象,经常在创建对象的函数中使用。

template inlinePassRefPtr adoptRef(T* p)
{
     adopted(p);
       return PassRefPtr(p, true);
}

同时值得说明的是,在RefPtr和PassRefPtr对象中对引用计数的操作,统一调用了

void refIfNotNull(T*ptr)和void derefIfNotNull(T* ptr),在PassRefPtr中对这两个函数的实现如下:

template REF_DEREF_INLINEvoid refIfNotNull(T* ptr)
{
       if (LIKELY(ptr != 0))

                ptr->ref();

}
template REF_DEREF_INLINE void derefIfNotNull(T* ptr)
{
     if (LIKELY(ptr != 0))
      ptr->deref();
}

在RefCountedBase中实现了ref和deref方法,从而实现了智能指针对其保存的引用计数的操作。

而RefPtr和PassRefPtr智能指针并不是只能用于RefCountedBase类对象的,只要重现实现void refIfNotNull(T* ptr)和void derefIfNotNull(T* ptr)函数,就能将智能指针用于其他有引用计数的对象或者结构,在WebCore中,RefPtrCairo.h中就定义了一系列这样的函数,这样在包含该头文件以后,我们就可以使用RefPtr和PassRefPtr管理Cairo的对象了


http://www.2cto.com/kf/201403/285512.html

[参考]http://webkit.org/coding/RefPtr.html


另一篇文章 :原帖地址 http://www.fenesky.com/blog/2014/06/18/webkit-smartPtr-RefPtr.html


WebKit智能指针历史

2005年以前,WebKit都使用的一套基于ref count引用计数的智能指针。2005年,Apple发现这套智能指针在WeKit中导致ref和deref使用混乱,一方面是出现很多内存泄漏。另一方面是效率很低,因为智能指针经常会需要被赋值以及作为参数传递,在这种情况下,每当有赋值或者参数传递的操作,都会导致ref count被加一或者减一,效率比较低。WebKit的工程师一直都在尝试着寻找一种易用且高效的智能指针的解决方案。后来,WebKit的工程师Maciej Stachowiak受到C++的auto ptr的启发:智能指针的传递,是指针ownership的传递,发明了RefPtr和PassRefPtr的引用计数智能指针。

RefPtr

RefPtr主要就是实现了ref和deref的机制。当对RefPtr与RefPtr进行赋值的时候,Raw pointer的ownership会发生转移

 1 template<typename T> template<typename U> 
 2 inline RefPtr<T>& RefPtr<T>::operator=(const RefPtr<U>& o)
 3 {
 4     T* optr = o.get();
 5     refIfNotNull(optr);
 6     T* ptr = m_ptr;
 7     m_ptr = optr;
 8     derefIfNotNull(ptr);
 9     return *this;
10 }

首先将新的Raw pointer通过o.get()取出来然后对其引用计数加一并保存在m_ptr当中,而对m_ptr的值的引用计数减一。从而实现了Raw pointer的ownership的转移:对旧的m_ptr进行引用计数减一,在m_ptr中保存新的Raw pointer。

单独使用RefPtr写程序的时候,会出现类似下面的代码:

 1 // example, not preferred style;
 2 // should use RefCounted and adoptRef (see below)
 3  
 4 RefPtr<Node> createSpecialNode()
 5 {
 6     RefPtr<Node> a = new Node;
 7     a->setSpecial(true);
 8     return a;
 9 }
10 
11 RefPtr<Node> b = createSpecialNode();

根据前面的一个文章《Return Value Optimization(RVO)返回值优化》 中所提到的,在编译器不对代码进行优化的情况下,上面这段代码会导致拷贝拷贝构造函数被调用很多次。即使编译器做了返回值优化,同样也会导致ref count被频繁的加一减一,没有达到提高效率的目的

PassRefPtr

PassRefPtr相对于RefPtr的一个最大优势是:PassRefPtr在向PassRefPtr或者RefPtr进行赋值和转化的时候,只发生ownership的转移,不对ref count进行操作。

PassRefPtr转化成RefPtr的代码如下:

1 template<typename T> template<typename U> 
2 inline RefPtr<T>::RefPtr(const PassRefPtr<U>& o)
3   : m_ptr(o.leakRef())
4 {
5 }

我们再来看看PassRefPtr::leakRef的实现:

1 template<typename T> inline T* PassRefPtr<T>::leakRef() const
2 {
3     T* ptr = m_ptr;
4     m_ptr = 0;
5     return ptr;
6 }

直接将Raw pointer返回并将当前PassRefPtr保存的Raw pointer赋为0,使得当前PassRerPtr失效。

那么再来看看PassRefPtr转化成RefPtr的操作,不难发现,其实这个操作只做了一件事情:Raw pointer的ownership转移到RefPtr中了,并没有对ref count进行操作。

如果我们单独使用PassRefPtr写程序

 1 // warning, will dereference a null pointer and will not work
 2  
 3 static RefPtr<Ring> g_oneRingToRuleThemAll;
 4 
 5 void finish(PassRefPtr<Ring> ring)
 6 {
 7     g_oneRingToRuleThemAll = ring;
 8     ...
 9     ring->wear();
10 }

由于g_oneRingToRuleThemAll = ring这个语句做了PassRefPtr的赋值操作,此时ring这个PassRefPtr已经失效,因为该赋值操作会将ring内部保存的Raw pointer值赋为0,也就是说,ring->wear()会出现空指针异常。

RefPtr和PassRefPtr结合使用

有了上面的分析,你会发现,为了达到易用和高效的目的,必须将RefPtr和PassRefPtr结合起来使用。

我们先来一个RefPtr、PassRefPtr和Raw Pointer之间的转换图

Alt Text

RefPtr向PassRefPtr转化

RefPtr::release的实现如下:

PassRefPtr<T> release()
{
    PassRefPtr<T> tmp = adoptRef(m_ptr);
    m_ptr = 0;
    return tmp;
}

先用当前RefPtr的m_ptr Raw pointer构造一个PassRefPtr,然后将m_ptr赋为0,使得当前RefPtr被复位失效。使得Raw pointer的ownership发生了转移,从原来的RefPtr转移到新的PassRefPtr中。

PassRefPtr向Raw pointer转化

直接通过上面提到的leakRef,返货Raw pointer,并把当前PassRefPtr复位使之失效。

RefPtr向Raw pointer转化

RefPtr要转化为Raw pointer,有2条途径:

  1. 直接使用get
  2. 先通过release转化为PassRefPtr,然后再将PassRefPtr转化为Raw pointer。

我们先来看看RefPtr::get的实现

T* get() const { return m_ptr; }

直接将Raw pointer返回,既没有发生ownership的转移,也没有对ref count进行操作。这是一个很危险的操作,意味着凡是通过get方式返回的Raw pointer,在任何情况下都不能对它进行ref count和delete操作,因为此时RefPtr是这个Raw pointer的owner,这些操作必须由RefPtr去完成。

当然,你还会发现,PassRefPtr也可以get方法转化成Raw pointer,原理跟RefPtr::get的相同,也是一个比较危险的操作,返回的Raw pointer的行为受限。

RefPtr转化成PassRefPtr,再由PassRefPtr转化成Raw pointer 是一个比较安全的操作,因为发生了ownership的转移,转化为PassRefPtr之后,再转化成Raw pointer就比较容易了。

PassRefPtr向RefPtr转化

这一操作,上面也提到了,只发生Raw pointer的ownership的转移,不对引用计数进行操作,原PassRefPtr被复位使之失效。

Ref Count的保存

上面讲了很多关于ref count引用计数的操作,这里我们需要明确一点,RefPtr和PassRefPtr并不保存Ref Count。Ref Count引用计数器是存放在Raw pointer的。RefPtr和PassRefPtr对Raw pointer进行ref和deref操作,也就意味着Raw pointer必须有ref和deref成员函数。WebKit中,一般需要使用RefPtr和PassRefPtr的类都继承于RefCounted或者ThreadSafeRefCounted。

RefCounted或者ThreadSafeRefCounted都继承自RefCountedBase。RefCountedBase中有个private成员变量就是unsigned m_refCount,并公开了ref()derefBase()接口。其中,ref()就是对m_refCount做加一操作。derefBase()则是对m_refCount做减一操作并返回true 或者false,来标示是否需要delete this指针。

看下RefCounted::deref的实现

void deref()
{
    if (derefBase())
        delete static_cast<T*>(this);
}

通过derefBase返回的值来确定是否需要delete this指针。因为derefBase会对m_refCount做减一操作,减一之后结果如果是0,说明这个对象已经没有引用了,derefBase就会返回true,则可以delete this指针。

对Raw pointer生命周期的影响

通过上面的分析,我们可以发现,RefPtr和PassRefPtr仅仅只是调用Raw pointer的ref()deref()来改变Raw pointer的m_refCount。Raw pointer自己根据当前m_refCount的值来决定是否需要delete this。

也就是说RefPtr和PassRefPtr并不直接参与Raw pointer的delete操作。这一点跟OwnPtr和PassOwnPtr有所不同,关于OwnPtr和PassOwnPtr后面会专门写一篇文章来讲解。

RefPtr和PassRefPtr的使用规则

  1. 参数的传递要使用PassRefPtr。
  2. 成员变量不能是PassRefPtr。
  3. 函数内部的局部变量要使用RefPtr。
  4. 函数返回值要使用PassRefPtr。
  5. 对于一个新create出来的对象,应该尽早的转化成RefPtr。
  6. 继承与RefCounted的对象应使用adoptRef转化成PassRefPtr。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值