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