Refptr and PassRefPtr basic

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

Refptr and PassRefPtr basic

Webkit中的很多对象都是引用计数的,所使用的方法是在类中加入ref和deref成员函数用于增加和减小引用计数。调用ref的次数一定要和调用deref的次数相同,当引用计数deref到1的时候,该对象会被释放。Webkit中的很多类通过继承RefCounted类模板来实现这种方法。

2005年,我们发现有很多的内存泄露,特别是在HTML editing code中误用了ref和deref调用的时候。

我们想用智能指针来解决这个问题。但是,一些验证性试验表明智能指针会使用额外的引用计数操作,会导致性能降低。例如,如果一个函数使用了智能指针作为参数,并且返回同样的智能指针作为返回值,单就是作为参数进行传递和返回该值,由于对象在智能指针之间传递,就会导致增加或者减少引用计数两到四次。所以我们在寻找一种理想的方式,能让我们使用智能指针,并且避免大量使用引用计数。

一个解决方法的灵感来自于C++标准类模板auto_ptr。使用auto_ptr的对象之间在赋值的时候,它的所有者也发生了改变,赋值者的引用数会变为0,对象归被赋值者所有。

Maciej Stachowiak设计了一对类模板,RefPtr和PassRefPtr,实现了这种方案,来解决WebCore的引用计数问题。

原始指针(Rawpointer)

当讨论类似RefPtr类模板这样的智能指针的时候,我们使用了原始指针这个术语来表示C++语言的内建指针类型。这里给出了使用原始指针实现的规范的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* title)

{

   if (title)

       title->ref();

   if (m_title)

       m_title->deref();

   m_title = title;

}

RefPtr

RefPtr是一个简单的智能指针类,它调用ref来增加引用,调用deref来减小引用。RefPtr在任何有ref和deref成员函数的对象上都会起作用,下面是用RefPtr写的setter例子

// example, not preferred style

 

class Document {

   ...

   RefPtr<Title> m_title;

}

 

void Document::setTitle(Title* title)

{

   m_title = title;

}

如果单独使用RefPtr也会导致引用计数的泛滥

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的时候,PassRefPtr中的原始指针会被设置为0;该操作不会导致引用计数有任何的变化。下面是用PassRefPtr的例子

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通过在赋值的时候,传递指针而不是增加减少引用计数的方式,降低了引用计数泛滥导致的性能降低问题。

但是,Safari team在实践中发现,PassRefPtr这种方式很容易出现问题。

static RefPtr<Ring>g_oneRingToRuleThemAll;

 

void finish(PassRefPtr<Ring> ring)

{

   g_oneRingToRuleThemAll = ring;

   ...

   ring->wear();

}

在ring->wear()的时候,ring中包含的指针已经被设置为0.为了避免发生这种情况,我们建议只在函数参数的返回值的时候,使用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而不改变引用计数。

PassRefPtr<Node> createSpecialNode()

{

   RefPtr<Node> a = new Node;

   a->setCreated(true);

   return a.release();//这里返回PassRefPtr,并且把a中的引用设置为0

}

 

RefPtr<Node> b = createSpecialNode();

这既保证了PassRefPtr的效率,又降低了技巧性的语义导致问题的机会。

 

与原始指针一起使用

如果一个函数的参数需要使用原始指针,那么可以使用RefPtr的get函数

printNode(stderr, a.get());

但是很多操作可以直接使用RefPtr或者PassRefPtr,而不用显式的调用get函数

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 rawpointers

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> node =adoptRef(rawNodePointer);

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

RefPtr和new objects

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

PassRefPtr<Node> Node::create()

{

   return adoptRef(new Node);

}

 

RefPtr<Node> e = Node::create();

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

// preferred style

PassRefPtr<Node> createSpecialNode()

{

   RefPtr<Node> a = Node::create();

   a->setCreated(true);

   return a.release();

}

 

RefPtr<Node> 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> DocumentLoader::create(constResourceRequest& request, const SubstituteData& data)

{

returnadoptRef(new DocumentLoader(request, data));

}

DocumentLoader继承自RefCounted<DocumentLoader>

 

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

 

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

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

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

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

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

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

PassRefPtr<T> RefPtr::release()

{

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

}

PassRefPtr<T>::leakRef(),该函数返回原始指针

template<typename T> inline T*PassRefPtr<T>::leakRef() const

    {

       T* ptr = m_ptr;

       m_ptr = 0;

       return ptr;

}

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

template<typename T> inlinePassRefPtr<T> adoptRef(T* p)

    {

       adopted(p);

       return PassRefPtr<T>(p, true);

}

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

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

template<typename T> REF_DEREF_INLINEvoid refIfNotNull(T* ptr)

    {

       if (LIKELY(ptr != 0))

           ptr->ref();

    }

 

   template<typename T> 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://baike.baidu.com/view/1391603.htm

http://zh.wikipedia.org/wiki/%E6%99%BA%E8%83%BD%E6%8C%87%E9%92%88
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值