本文将从WebKit源码中解析WebKit智能指针的用法。进入正题之前,先还是要仔细看看官方文档。不管能否看明白还是要先看看这篇文章,毕竟这是本文最主要的参考文档。
文档里已提到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++内存管理固然复杂,但也有简单的方法来控制这个复杂的范围的。
作者:cutesource