月度归档:2010年09月

webkit技术译文系列(二):RefPtr和PassRefPtr基础

原文链接:http://webkit.org/coding/RefPtr.html

历史

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)的设置函数:

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

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

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

// example, not preferred style; should use RefCounted and adoptRef (see below)
RefPtr<Node> createSpecialNode()
{
    RefPtr<Node> a = new Node;
    a->setSpecial(true);
    return a;
}
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,操作不改变引用计数。让我们来看一个新的版本的例子:

// example, not preferred style; should use RefCounted and adoptRef (see below)
PassRefPtr<Node> createSpecialNode()
{
    PassRefPtr<Node> a = new Node;
    a->setSpecial(true);
    return a;
}
RefPtr<Node> b = createSpecialNode();

节点对象的初始引用计数值为0,当它赋值给a后,引用计数值增加到1