分享

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

blackkettle 发表于 2013-12-15 23:53:03 [显示全部楼层] 回帖奖励 阅读模式 关闭右栏 0 8120
本文将从WebKit源码中解析WebKit智能指针的用法。进入正题之前,先还是要仔细看看官方文档:http://www.webkit.org/coding/RefPtr.html。不管能否看明白还是要先看看这篇文章,毕竟这是本文最主要的参考文档。
文档里已提到2005之前,WebKit基于RefCounted来管理对象的销毁。
RefCountedRefCounted原理很简单,就是最经典的引用计数的方式。它的源码也很简单,看看它最重要的两个方法,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(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和PassRefPtrRefPtr的思路很简单,就是要把上面例子自动化,自动地在各项操作中加上deref和ref,先来看看它最关键几个方法的源码:
    template inline RefPtr& RefPtr::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 m_title;
}
void Document::setTitle(Title* title)
{
    m_title = title;
}但这虽然简洁了代码,但没有简洁代码实际的执行过程,所以文档里就提到了频繁ref和deref的问题,比如以下代码:
RefPtr createSpecialNode()
{
    RefPtr a = new Node;
    a->setSpecial(true);
    return a;
}
RefPtr b = createSpecialNode();这段代码最终的结果是Node对象的引用为1,但结合RefPtr的源码,我们可知,其中会有多次来回的ref和deref,文档里也解释了这个过程。所以就需要一种机制来做到参数传递时可以附带传递引用值,而不是通过正负抵消的方式来保证引用的不变,这就是PassRefPtr存在的价值。现在看看PassRefPtr几个关键方法的源码:
template inline PassRefPtr adoptRef(T* p)
    {
        adopted(p);
        return PassRefPtr(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 inline T* PassRefPtr::leakRef() const
    {
        T* ptr = m_ptr;
        m_ptr = 0;
        return ptr;
    }
从中可以知道,PassRefPtr主要用于参数传递中,当传递完成后,被PassRefPtr包装的对象就会被销毁,并且整个过程中不改变对象引用。那么基于PassRefPtr重构上例的代码:
PassRefPtr Node::create()
{
    return adoptRef(new Node);
}
RefPtr e = Node::create();最终效果就是Node的引用为1,并且中间没有引用的变化。但是,PassRefPtr是不能替代RefPtr的,因为被赋值后,它就是的NULL了,再调用就会有空指针的错误。所以它们俩的引用场景很明确:
  • RefPtr:用于希望能自动管理对象回收的地方。
  • PassRefPtr:用于方法参数和返回值参数上。两者总是配合使用。
    RefPtr和PassRefPtr都是从RefCounted演变而来,并且只能用于继承自RefCounted的对象,所以有一定的局限性,也就有了OwnPtr和PassOwnPtr用武之地。
    OwnPtr和PassOwnPtrOwnPtr不是基于计数来管理对象销毁,它简单又暴力,先看看它几个关键方法的源码:
        template template inline OwnPtr::OwnPtr(const PassOwnPtr[U]& o)
            : m_ptr(o.leakPtr())
        {
        }
        template inline PassOwnPtr OwnPtr::release()
        {
            PtrType ptr = m_ptr;
            m_ptr = 0;
            return adoptPtr(ptr);
        }
        template inline OwnPtr& OwnPtr::operator=(const PassOwnPtr& o)
        {
            PtrType ptr = m_ptr;
            m_ptr = o.leakPtr();
            ASSERT(!ptr || m_ptr != ptr);
            deleteOwnedPtr(ptr);
            return *this;
        }
    它的语义就是这个对象仅仅只能由我来管理,别人都不能引用,别人赋值给我,就自动赋值为NULL,仅我拥有此对象的引用,当我的作用域完了后,会自动销毁。它比较适合不是从RefCounted继承下来的对象,并且生命周期由我控制的场景。
    好了,经过上面的分析,基本上把WebKit的智能指针的原理和使用场景搞清楚了。得到的启发是,C++内存管理固然复杂,但也有简单的方法来控制这个复杂的范围的。
  • 没找到任何评论,期待你打破沉寂

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    关闭

    推荐上一条 /2 下一条