现在的位置: 首页 > 综合 > 正文

探索WebKit内核(五)—— 智能指针解析:RefCounted, RefPtr, PassRefPtr, OwnPtr和PassOwnPtr

2012年08月04日 ⁄ 综合 ⁄ 共 3631字 ⁄ 字号 评论关闭

本文将从WebKit源码中解析WebKit智能指针的用法。进入正题之前,先还是要仔细看看官方文档:http://www.webkit.org/coding/RefPtr.html。不管能否看明白还是要先看看这篇文章,毕竟这是本文最主要的参考文档。

文档里已提到2005之前,WebKit基于RefCounted来管理对象的销毁。

RefCounted

RefCounted原理很简单,就是最经典的引用计数的方式。它的源码也很简单,看看它最重要的两个方法,ref和deref:

    void ref()
    {
#if CHECK_REF_COUNTED_LIFECYCLE
        ASSERT(m_verifier.isSafeToUse());
        ASSERT(!m_deletionHasBegun);
        ASSERT(!m_adoptionIsRequired);
#endif
        ++m_refCount;
    }
    void deref()
    {
        if (derefBase())
            delete static_cast<T*>(this);
    }
    // Returns whether the pointer should be freed or not.
    bool derefBase()
    {
#if CHECK_REF_COUNTED_LIFECYCLE
        ASSERT(m_verifier.isSafeToUse());
        ASSERT(!m_deletionHasBegun);
        ASSERT(!m_adoptionIsRequired);
#endif

        ASSERT(m_refCount > 0);
        if (m_refCount == 1) {
#if CHECK_REF_COUNTED_LIFECYCLE
            m_deletionHasBegun = true;
#endif
            return true;
        }

        --m_refCount;
#if CHECK_REF_COUNTED_LIFECYCLE
        // Stop thread verification when the ref goes to 1 because it
        // is safe to be passed to another thread at this point.
        if (m_refCount == 1)
            m_verifier.setShared(false);
#endif
        return false;
    }

抛开一些状态的维护不看,它其实就是在调用ref时,内部计数器加1,调用deref时计数器减1,当减到1时,就自动delete。所以一句话,它就是通过内部计数器来判断外部的引用从而实现自动销毁对象。这种方法虽然实现简单,但造成了调用者的麻烦,比如文档里提到的例子:

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;
}

简单的一个set方法,就需要来回调用ref和deref,在更复杂的场景下难免导致ref和deref的不对称,从而造成本该销毁却没销毁,或是错销毁的情况。后来,WebKit就引入了RefPtr, PassRefPtr, OwnPtr和PassOwnPtr来解决这个问题。

RefPtr和PassRefPtr

RefPtr的思路很简单,就是要把上面例子自动化,自动地在各项操作中加上deref和ref,先来看看它最关键几个方法的源码:

    template<typename T> inline RefPtr<T>& RefPtr<T>::operator=(T* optr)
    {
        refIfNotNull(optr);
        T* ptr = m_ptr;
        m_ptr = optr;
        derefIfNotNull(ptr);
        return *this;
    }

    ALWAYS_INLINE RefPtr(T* ptr) : m_ptr(ptr) { refIfNotNull(ptr); }

看字面意思就能很清楚的知道,当把一个对象赋值给RefPtr包装过的对象后,它会先被赋值的对象ref,然后再给自己原来的对象deref,这实际上就是上例中setTitle的过程,所以改写后就极大简洁了代码:

class Document {
    ...
    RefPtr<Title> m_title;
}

void Document::setTitle(Title* title)
{
    m_title = title;
}

但这虽然简洁了代码,但没有简洁代码实际的执行过程,所以文档里就提到了频繁ref和deref的问题,比如以下代码:

RefPtr<Node> createSpecialNode()
{
    RefPtr<Node> a = new Node;
    a->setSpecial(true);
    return a;
}

RefPtr<Node> b = createSpecialNode();

这段代码最终的结果是Node对象的引用为1,但结合RefPtr的源码,我们可知,其中会有多次来回的ref和deref,文档里也解释了这个过程。所以就需要一种机制来做到参数传递时可以附带传递引用值,而不是通过正负抵消的方式来保证引用的不变,这就是PassRefPtr存在的价值。现在看看PassRefPtr几个关键方法的源码:

template<typename T> inline PassRefPtr<T> adoptRef(T* p)
    {
        adopted(p);
        return PassRefPtr<T>(p, true);
    }

PassRefPtr(T* ptr, bool) : m_ptr(ptr) { }

PassRefPtr& operator=(const PassRefPtr&) { COMPILE_ASSERT(!sizeof(T*), PassRefPtr_should_never_be_assigned_to); return *this; }
template<typename T> inline T* PassRefPtr<T>::leakRef() const
    {
        T* ptr = m_ptr;
        m_ptr = 0;
        return ptr;
    }

从中可以知道,PassRefPtr主要用于参数传递中,当传递完成后,被PassRefPtr包装的对象就会被销毁,并且整个过程中不改变对象引用。那么基于PassRefPtr重构上例的代码:

PassRefPtr<Node> Node::create()
{
    return adoptRef(new Node);
}

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

最终效果就是Node的引用为1,并且中间没有引用的变化。但是,PassRefPtr是不能替代RefPtr的,因为被赋值后,它就是的NULL了,再调用就会有空指针的错误。所以它们俩的引用场景很明确:

  • RefPtr:用于希望能自动管理对象回收的地方。
  • PassRefPtr:用于方法参数和返回值参数上。

两者总是配合使用。

RefPtr和PassRefPtr都是从RefCounted演变而来,并且只能用于继承自RefCounted的对象,所以有一定的局限性,也就有了OwnPtr和PassOwnPtr用武之地。

OwnPtr和PassOwnPtr

OwnPtr不是基于计数来管理对象销毁,它简单又暴力,先看看它几个关键方法的源码:

    template<typename T> template<typename U> inline OwnPtr<T>::OwnPtr(const PassOwnPtr<U>& o)
        : m_ptr(o.leakPtr())
    {
    }

    template<typename T> inline PassOwnPtr<T> OwnPtr<T>::release()
    {
        PtrType ptr = m_ptr;
        m_ptr = 0;
        return adoptPtr(ptr);
    }

    template<typename T> inline OwnPtr<T>& OwnPtr<T>::operator=(const PassOwnPtr<T>& o)
    {
        PtrType ptr = m_ptr;
        m_ptr = o.leakPtr();
        ASSERT(!ptr || m_ptr != ptr);
        deleteOwnedPtr(ptr);
        return *this;
    }

它的语义就是这个对象仅仅只能由我来管理,别人都不能引用,别人赋值给我,就自动赋值为NULL,仅我拥有此对象的引用,当我的作用域完了后,会自动销毁。它比较适合不是从RefCounted继承下来的对象,并且生命周期由我控制的场景。

好了,经过上面的分析,基本上把WebKit的智能指针的原理和使用场景搞清楚了。得到的启发是,C++内存管理固然复杂,但也有简单的方法来控制这个复杂的范围的。

抱歉!评论已关闭.