WebKit中RefPtr和PassRefPtr的使用

WebKit中的许多对象是引用计数的(reference counted),采用的模式就是类具有ref和deref成员函数增加和减少引用计数。每个ref调用必须有一个deref与之匹配。当在引用计数值为1的对象上调用deref方法时,对象删除。WebKit中的许多类通过继承RefCounted类模板应用该模式。

时间回溯到2005年,我们发现存在许多由于不正确调用ref和deref而引起的内存泄露,特别是HTML编辑的代码。

我们希望使用智能指针来减少这一问题。但是早期的试验表明智能指针会进行额外的引用计数处理而影响性能。例如,一个函数有一个智能指针的参数并返回该指针指针作为返回值,仅仅传入该参数并返回该值会进行2到4次的增加和减少引用计数值,因为对象从一个智能指针转移到另一个上。因此我们寻找一种方式来让我们使用智能指针同时避免引用计数跳变(churn).

我们从C++标准类模板auto_ptr获得灵感,这些对象实现了一种模型,赋值即是归属关系转移(transfer of ownership),当您从一个auto_ptr赋值到另一个,贡献值变成0.(When you assign from one auto_ptr to another, the donor becomes 0.)

Maciej Stachowiak设计了一对类模板,RefPtr和PassRefPtr,实现这一模式来解决WebKit中恼人的引用计数问题。

原始指针(Raw pointers)

当我们讨论诸如RefPtr类模板之类的智能指针时,通常使用原始指针(raw pointer)指代C++语言中内置的指针类型。下面是经典的使用原始指针(raw pointer)的设置函数:

 

  1. // example, not preferred style  
  2. class Document {  
  3.     ...  
  4.     Title* m_title;  
  5. }  
  6. Document::Document()  
  7.     : m_title(0)  
  8. {  
  9. }  
  10. Document::~Document()  
  11. {  
  12.     if (m_title)  
  13.         m_title->deref();  
  14. }  
  15. void Document::setTitle(Title* title)  
  16. {  
  17.     if (title)  
  18.         title->ref();  
  19.     if (m_title)  
  20.         m_title->deref();  
  21.     m_title = title;  
  22. }  

 RefPtr

RefPtr是一个简单的智能指针类,它对来值(incoming value)调用ref,对去值(outgoing value)调用deref。RefPtr可用于任何有ref和deref成员函数的对象。下面是使用RefPtr写的设置函数实例:

 

  1. // example, not preferred style  
  2.    
  3. class Document {  
  4.     ...  
  5.     RefPtr<Title> m_title;  
  6. }  
  7. void Document::setTitle(Title* title)  
  8. {  
  9.     m_title = title;  
  10. }  

 单独使用RefPtr可能会导致引用计数跳变(churn)。

 

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

 

 

出于讨论考虑,我们假设节点对象的引用计数起始值为0(稍后更多),当它赋值给a后,引用计数值增加到1。创建返回值后引用计数值又增加到2,接下来a销毁,引用计数值又减少到1。然后创建b后引用计数值增加到2,再下来createSpecialNode返回值销毁后,引用计数值减到1。

(如果编译器实现了返回值优化(return value optimization),就会少一次引用计数增加和减少。)

引用计数跳变(churn)在函数参数和返回值都涉及的情况下更严重,解决的方法就是PassRefPtr。

PassRefPtr

PassRefPtr和RefPtr相似,但存在一处不同,当您拷贝一个PassRefPtr或者赋PassRefPtr值给RefPtr或另一个PassRefPtr时,原来的指针值设置为0,操作不改变引用计数。让我们来看一个新的版本的例子:

 

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

 节点对象的初始引用计数值为0,当它赋值给a后,引用计数值增加到1。当返回值创建后,指针a被赋值为0。当b值创建后,返回值被赋值为0。然而,引用计数保持1不变。

     由于PassRefPtr类型指针赋值给另一个指针后本指针变成为0,所以使用PassRefPtr编程很容易容易导致错误。如下下列代码所示:

 

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

 

当wear方法被调用时,ring指针已经变为0了。为了避免这种情况发生,建议只是用PassRefPtr类型作为函数参数类型和返回值类型。并且,将参数类型为PassRefPtr的参数赋值给本地的RefPtr类型指针进行操作。如下:

 

  1. static RefPtr<Ring> g_oneRingToRuleThemAll;
  2. void finish(PassRefPtr<Ring> prpRing)
  3. {
  4.     RefPtr<Ring> ring = prpRing;
  5.     g_oneRingToRuleThemAll = ring;
  6.     ...
  7.     ring->wear();
  8. }

 

Mixing RefPtr and PassRefPtr

    除了函数传递参数和返回值时使用PassRefPtr类型外,建议使用RefPtr作为计数指针类型。但是,经常会需要RefPtr类型指针象PassRefPtr型指针一样传递指针的所有权。RefPtr类型有一个release成员函数,它通过设置原始的RefPtr指针为0,同时构建一个PassRefPtr类型指针,从而达到不改变指针引用计数的目的。示例代码如下:

 

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

 

上述代码与前面将a定义成PassRefPtr类型的示例代码具有相同的效果。这种用法,不仅保持了PassRefPtr类型指针的高效的特性,同时又避免了PassRefPtr容易出错的问题。

Mixing with raw pointers

      当要获得RefPtr类型中的原始指针时,需要通过get函数来获得原始指针。

下面示例打印原始指针的值:

 

  1. printNode(stderr, a.get());

然而,RefPtr类型和PassRefPtr类型变量无需显性的调用get的函数就可以直接完成大多数的操作。

 

示例如下:

 

  1. RefPtr<Node> a = createSpecialNode();
  2. Node* b = getOrdinaryNode();
  3. // the * operator
  4. *= value;
  5. // the -> operator
  6. a->clear();
  7. // null check in an if statement
  8. if (a)
  9.     log("not empty");
  10. // the ! operator
  11. if (!a)
  12.     log("empty");
  13. // the == and != operators, mixing with raw pointers
  14. if (== b)
  15.     log("equal");
  16. if (!= b)
  17.     log("not equal");
  18. // some type casts
  19. RefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);

 

通常,RefPtr和PassRefPtr强制实施一个简单的规则,就是成对调用ref和deref,保证编程者不会漏掉对deref的调用。说的ref和deref就不能不说一下RefCounted类,这个类实现了指针的引用计数功能,提供了ref和deref方法。而RefPtr和PassRefPtr只是完成了对ref和deref的自动调用功能,因此这里所说的原始指针(raw pointer)通常是指继承自RefCounted类的指针,换句话说,是具有ref和deref功能的指针。这样的原始指针可以通过adoptRef函数转换成RefPtr类型。

  1. // warning, requires a pointer that already has a ref
  2. RefPtr<Node> node = adoptRef(rawNodePointer);

 

从RefPtr类型转换成一个原始指针并且不改变引用计数,可以使用PassRefPtr提供的leakRef函数。

 

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

 

由于leakRef很少被使用,因此只有PassRefPtr类具有这个方法。RefPtr类型的变量使用此方法使,需要先调用release方法,然后才能调用leakRef方法。

RefPtr and new object

在以上讨论的例子中,对象的假设引用计数是从0开始的。这样做的目的为了讲解简单明了,但是RefCounted类的引用计数并不是从0开始的。在构建ReCounted类的构造函数中,引用计数被赋值为1。当deref被调用时,如果引用计数等于1,则删除这个对象。由于ref和deref是成对出现的,为了不遗失调用deref,推荐的用法是在创建对象时应该立即调用adoptRef。在WebCore中,使用create方法代替new方法去创建对象。示例如下:

 

  1. // preferred style
  2.  
  3. PassRefPtr<Node> Node::create()
  4. {
  5.     return adoptRef(new Node);
  6. }
  7. RefPtr<Node> e = Node::create();

 

因为adoptRef和PassRefPtr被应用,上述用法是非常高效的。以引用计数为1创建对象,而整个过程没有再操作引用计数。

 

  1. // preferred style
  2.  
  3. PassRefPtr<Node> createSpecialNode()
  4. {
  5.     RefPtr<Node> a = Node::create();
  6.     a->setCreated(true);
  7.     return a.release();
  8. }
  9. RefPtr<Node> b = createSpecialNode();

 

对象node通过create函数创建,赋值给a并release,最后赋值给b,整个过程没有触及引用计数。

RefCounted类会进行运行时刻检查,如果创建一个对象,调用ref和deref方法而没有最先调用adoptRef函数,将会声明一个错误。

Guidelines

在WebKit编程中使用RefPtr和PassRefPtr需要遵循如下规则:
 
Local variables

 

如果指针的所有权和生命周期可以被保证,一个本地变量可以是一个原始指针。

如果代码需要获得指针的所有权或者确保指针的生命周期,本地变量应该是RefPtr类型。

本地变量永远都不能是PassRefPtr类型。

Data members

如果所有权和生命周期可以被保证,数据成员可以是一个原始指针。

如果类需要获得数据成员指针所有权并保证其生命周期,则数据成员需要定义为RefPtr类型。

数据成员永远都不能是PassRefPtr类型。

Function arguments

如果一个函数不需要获得参数对象的所有权,参数应该使用原始指针。

如果函数需要获得一个对象的所有权,参数应个是一个PassRefPtr类型。这包括大多数的setter函数。除非参数非常简单,在函数的开始PassRefPtr参数应用赋值给一个本地的RefPtr变量。

Function Results

如果一个函数的返回值是一个对象,但是所有权没有被传递,这个返回结果应用是一个原始指针。

如果函数的返回值是一个被创建的对象,或者对象的所有权被传递,返回值应该是PassRefPtr类型。由于本地类型通常是RefPtr,因此在返回时需要调用release方法将RefPtr类型转换成PassRefPtr类型。

New Object

新创建的指针对象应该尽快放在RefPtr类型中,这样可以使智能指针自动完成所有的引用计数。

对于RefCounted类型指针,上述的操作应该由adoptRef函数完成。

最好的用法是使用私有的构造函数和公有的create函数,通过返回一个PassRefPtr指针从而实现对象的创建。

 

 

在看code的时候,发现了 RefPtr PassRefPtr 这样的结构体, 而且用的还很多 , 网上查找后, 看到这样的文章, 先贴出来给大家 ! 
  (1)http://blogt.chinaunix.net/space.php?uid=7448773&do=blog&id=603837
  (2)http://www.webkit.org/coding/RefPtr.html 
  
  其实简单的说就是, webkit里,有很多的new , 很多实例的使用, 但传递的,则是指针, 因此在维护方面, 很麻烦; 但好的一个是,webkit team引入了“引用计数”的改变, 也就是当有指针赋值的时候, 会根据这个指针的引用情况,来分别的做ref deref,而当作deref的时候, 判断其计数值,而决定是否需要做destory动作; 看代码
   template inline RefPtr& RefPtr::operator=(T* optr)
   {
   refIfNotNull(optr); --------- 针对T point的引用, 先对其做ref, 这样增加其count数
   T* ptr = m_ptr; -----记录当前的值;为后面做deref准备
   m_ptr = optr; ----- 简单的指针赋值
   derefIfNotNull(ptr);-----对先前的m_ptr, 做deref操作,因为此时,m_ptr已经被赋有新的取值,需要把先前的值做deref动作,并以此来决定是否需要做destory操作
   return *this;
   }
  由此可以看到,在RefPtr赋值的时候,会先后调用ref 和 deref 操作, 这样就完成了从一次指针, 传递到另外一个指针的目的。 
  
  RefPtr 定义的实例,在constructor的时候,会去调用ref 函数, 而其deconstructor 函数会去做deref函数,并最终在decontructor判断引用的情况, 而决定是否需要做del动作; 而且RefPtr 的 release 会直接把Refptf给清零为NULL; 因为它会返回PassRefPtr,也就是把这个point的使用权限, 丢给了PassRefPtr,并最终传递给其它变量。 
  
   PassRefPtr 和 RefPtr差主要体现在赋值的时候,有所差别,如下
   template inline PassRefPtr& PassRefPtr::operator=(const PassRefPtr& ref)
   {
   T* ptr = m_ptr;
   m_ptr = ref.releaseRef();
   derefIfNotNull(ptr);
   return *this;
   }
  这里的关键是, 当调用赋值的时候 ,先前的ref会调用自身的releaseRef()函数, 而这个函数,则会把ref给赋值为NULL,再把值转出来, 因此这样的话,当有PassRefPtr的赋值完成后,会把先前的ref清为NULL,这样就保证了只有这个一份的copy。
  
  因此有建议这样:“只是用PassRefPtr类型作为函数参数类型和返回值类型,并且,将参数类型为PassRefPtr的参数赋值给本地的RefPtr类型指针进行操作”。 这样的目的是因为,PassRefPtr的赋值,会导致先前的变为NULL。 
  
   一般由于ref和deref是成对出现的,而且一般都是自己来做check; 当做deref的时候, 如果count==1, 则会对实例做deconstructor,但为了防止很多的class忘记做deref,因此一般都是在calss constructor的时候,使用adoptRef 把class 实例转换给PassRefPtr,因为在class的constructor中,因为class从RefCounted 继承过来,其bass constructor会设置count=1,这样,接下来就把整个实例交由PassRefPtr去处理了 。 
  
  还有 OwnPtr 和 PassOwnPtr , 这一组和RefPtr & PassRefPtr 类似,其区别还没看懂“Think of OwnPtr like RefPtr, except for singularly-owned objects instead of reference-counted objects.”

 

 

 

RefPtr,它的名字说明了它是跟引用技术相关的智能指针。跟RefPtr相关的类是RefCounted这个模版类。

RefCounted

class FrameView : public RefCounted<FrameView> {
public:
   ~FrameView();
}


于是FrameView就成为一个”被引用技术托管”的类了。它(RefCounted)提供以下几个核心方法:

ref();
deref();

分别是对FrameView进行增减引用的操作。当引用计数减到0时,对象就会被删除。

为啥要用模版类?

不知道你注意到没有,FrameView的引用计数减为零并删除对象实例,其实是在基类RefCounted中实现的:delete this;
这就带来一个问题,首先析构函数必须是虚的,否则会带来父类的内存泄漏。用模版类呢就可以直接调用托管类(FrameView)的析构函数而不依赖虚函数:

delete static_cast(this);

RefPtr

终于讲到RefPtr了。一个需要引用计数托管的类,要继承RefCounted。每次我们都可以用ref(), deref()方法来增减。但这样总是不方便的,RefPtr就是用来处理这个问题的。

class FrameView : public RefCounted<FrameView> {
};
 
RefPtr<FrameView> myView = adoptRef(new FrameView);
RefPtr<FrameView> anotherView = myView;

当RefPtr被赋值的时候,它会调用FrameView::ref(),当RefPtr被析构的时候,会调FrameView::deref()。

PassRefPtr

PassRefPtr负责传递所有权的中间类。引用计数托管的类传递所有权是件很奇怪的事情,一般很少用。仅在创建裸指针的时候使用。

线程安全

问RefCounted是否线程安全呢?答曰:否。这也是为了性能考虑吧,因为WebKit渲染其实仅在一个线程完成,没必要对每次引用计数的管理都加锁。如果需要线程安全的引用计数管理,怎么办呢?

class FrameView : public CrossThreadRefCounted<FrameView> {
};

于是就可以用RefPtr来跨线程管理它了。

RefPtr就先讲到这儿了。

posted @ 2013-04-14 20:01  JustinYo  阅读(1110)  评论(0编辑  收藏  举报