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

本文將從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++內存管理固然複雜,但也有簡單的方法來控制這個複雜的範圍的。




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