RefPtr and PassRefPtr基础 -- WebKit中的引用计数

历史

在WebKit中,很多对象采用了引用计数。使用的模式是,通过类成员ref和deref来增加和减少引用计数。每一个ref调用必须有一个相应的deref。当一个对象的引用计数是1时,在其上调用deref将会删除该对象。在WebKit中,很多类是通过继承RefCounted类模板来实现这种模式的。
早在2005年,我们发现有太多内存泄露了,尤其是在HTML编辑代码中,这是由于对ref和deref的误用造成的。
我们想通过使用智能指针来改善内存泄露问题。然而,早期的一些实验证实智能指针会导致额外的引用计数操作,这会损害性能。例如,对于一个带有一个智能指针参数,并返回同一个智能指针的函数而言,当我们调用该函数把一个对象从一个智能指针移动到另一个智能指针时,传递参数和返回值就会递增和递减引用计数2-4次。所以我们就想找一种方法,既可以使用智能指针,又能够避免引用计数操作的开销。
解决的灵感来自C++的标准类模板auto_ptr。对auto_ptr赋值就是转移所有权。当你把一个auto_ptr赋值个另一个时,原来的auto_ptr变为0。
Maciej Stachowiak发明了一对类模板,RefPtr 和 PassRefPtr,它们实现了WebCore的侵入式引用计数方案。

原始指针

在讨论智能指针,比如RefPtr类模板时,我们使用术语raw pointer来指称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是一个简单的智能指针类,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会带来额外的引用计数操作开销

// 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();

为便于讨论,我们假定一个node对象开始时引用计数为0。当它被赋值给a时,引用计数增加到1。创建返回值时引用计数被增加到2,然后,当a被销毁时,引用计数被减少,变为1. 创建b时引用计数又增加到2,然后createSpecialNode的返回值被销毁 ,引用计数又减少到1。
如果编译器实现了返回值优化,引用计数增加和减少的次数可能会减少。
如果参数和返回值都是智能指针,引用计数附带的开销就更大了。解决这一个问题的方法是使用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();

node对象初始引用计数为0。当它赋值给a时,引用计数增加到1. 当返回值PassPtrRef被创建时,a被设置0. 当b被创建时,返回值又被设为0.
然而,当Safari团队开始使用PassRefPtr进行编程时,我们发现,当赋值给另一个变量时,原来的指针变为0这条规则很容易导致错误。

// warning, will dereference a null pointer and will not work

static RefPtr <Ring> g_oneRingToRuleThemAll;

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

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

我们推荐在除了传递参数或从一个函数返回值的所有场合使用RefPtr,但有时,你有一个RefPtr,而且希望像PassRefPtr一样可以转移它的所有权。RefPtr有一个叫做release的成员函数可以做到。它把原来的RefPtr设为0,并构造一个PassRefPtr,引用计数保持不变。

// example, not preferred style; should use RefCounted and adoptRef (see below)

PassRefPtr<Node> createSpecialNode()
{
        RefPtr<Node> a = new Node;
        a->setCreated(true );
        return a .release();
}

RefPtr<Node> b = createSpecialNode();

这既保持了PassRefPtr的效率,同时也降低了它相对复杂的语义可能引起问题的机率。

混合使用raw pointers

当使用一个RefPtr调用一个需要一个raw pointer的函数时,使用get函数。

printNode(stderr , a.get());

不过,许多操作其实可以直接使用RefPtr和PassRefPtr直接完成,而不用借组一个显示的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和PassRefPtr都执行一条简单的规则;通过ref和deref调用达到平衡,这是建立在用户不会忘记调用deref的基础上的。不过有这种情况,我们有一个raw pointer,它已经有一个引用计数,而且我们想要转移它的所有权,这时就可以使用adoptRef函数。

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

PassRefPtr提供了函数leakRef,它可以再不改变引用计数的情况下,把一个RefPtr转移给一个raw pointer。

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

因为leakRef很少用到,所以只在PassRefPtr类中提供,所以,需要先调用release,在调用leakRef

上面提到三个函数,比较容易混淆,这里总结一下
release:  使RefPtr可以像PassRefPtr一样转移所有权,它是RefPtr<T>的一个成员函数,实现如下
PassRefPtr<T > RefPtr<T >::release() { PassRefPtr<T > tmp = adoptRef( m_ptr); m_ptr = nullptr; return tmp ; }

adoptRef: 把一个raw pointer转移给一个PassRefPtr,它是一个全局函数
template<typename T> inline PassRefPtr< T> adoptRef (T* p)
    {
        adopted(p );
        return PassRefPtr <T>(p, PassRefPtr< T>::Adopt );
    }

leakRef:可以实现把一个RefPtr转移给一个raw pointer,它是PassRefPtr的一个成员函数,实现如下
 template< typename T > inline T* PassRefPtr< T>::leakRef () const
    {
        T* ptr = m_ptr;
        m_ptr = nullptr ;
        return ptr ;
    }

RefPtr有这样两个操作
template< typename U > RefPtr(const PassRefPtr< U>&);
template<typename U> RefPtr(PassRef< U>);

RefPtr和新对象

在前面我们提到引用计数为0的对象。然而,出于效率和简单性考虑,RefCounted类的引用计数根本不会为0。对象被创建时,引用计数是1. 最好的编程习惯是立刻把新创建的对象放进一个RefPtr,这样就不会说用后之后忘记调用deref函数。这意味着,任何时候,new对象时都应该立即调用adoptRef。在WebCore中,我们使用叫做create的函数来替代对new的直接调用。
// preferred style

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();

这些改进措施目的:自动管理资源,改善内存泄露情况。智能指针是通过引用计数的方式实现的,所谓鱼和熊掌不可兼得,这必然带来引用计数操作开销问题。因此要考虑尽量减少这样的开销。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章