2005年之前,Webkit中很多对象都采用引用计数的方式。它们通过继承RefCounted类模板来实现这种模式。RefCounted主要是实现了ref()和deref()两个函数。在需要引用对象时要调用ref()增加引用计数,在不再需要对象时,要调用deref()函数减少引用计数。ref()和deref()需要成对出现。这和使用new/delete一样,多调用、少调用、没调用的问题总是时有发生。如果能由编译器自动完成ref, deref的调用,C/C++编程的bug至少也可以减少一半以上了。
ps:虽然C/C++编程的大部分问题最终表现是内存问题,但其实根源可能还是逻辑和流程问题。话说回来,如果编译器能自动管理内存,确实可以大大减轻程序员的负担。RefCounted几个主要的函数实现如下:
void ref() { ++m_refCount; } // Returns whether the pointer should be freed or not. bool derefBase() { unsigned tempRefCount = m_refCount - 1; if (!tempRefCount) { return true; } m_refCount = tempRefCount; return false; } void deref() { if (derefBase()) delete static_cast<T*>(this); }
RefPtr可以应用于任何具有ref()和deref()成员函数的对象,它通过自动调用对象的ref()和deref()来实现指针的智能管理。Webkit中很多对象通过继承RefCounted实现引用计数模式(拥有ref()和deref()函数)。
RefPtr在传入值时自动调用ref()来增加引用计数,在传出值时自动调用deref()来减少引用计数。我们知道在调用deref()时,如果引用计数为1,就会删除相应对象。
RefPtr是用来管理指针的,也就是说你得先有指针吧,指针怎么来的,万变不离其中,还得new出来。new 出来的我们叫裸指针,所谓交给RefPtr管理就是把new出来的裸指针传递给一个RefPtr对象。智能指针内部呢,会在合适的时候delete你交给它管理的裸指针。
通过adoptRef()把一个裸指针赋值给一个RefPtr。任何时候,new对象时都应该立即调用adoptRef,这样就不会有过后忘记调用deref()的问题。
RefPtr<Foo> foo = adoptRef(new Foo());
处于效率考虑,Foo在创建时就把引用计数赋为1,因此不能把new的对象直接赋值给RefPtr,因为那样做,将导致new的对象永远无法释放。所以需要使用adoptRef来转移所有权。
RefCountedBase() : m_refCount(1) { }
adoptRef的实现(PassRefPtr.h):
template<typename T> inline PassRefPtr<T> adoptRef(T* p) { adopted(p); return PassRefPtr<T>(p, PassRefPtr<T>::Adopt); }
其中的adopted实际上什么也没有做, 定义如下(PassRef.h):
inline void adopted(const void*) { }
接下来调用的PassRefPtr定义如下:
enum AdoptTag { Adopt }; PassRefPtr(T* ptr, AdoptTag) : m_ptr(ptr) { }
该函数以一个裸指针为参数创建一个PassRefPtr临时对象。在上面的例子中,实际是赋值给一个RefPtr对象,用到了下面的类型转换函数:
template<typename T> template<typename U> inline RefPtr<T>::RefPtr(const PassRefPtr<U>& o) : m_ptr(o.leakRef()) { }
template<typename T> inline T* PassRefPtr<T>::leakRef() const { T* ptr = m_ptr; m_ptr = nullptr; return ptr; }
leakRef()把一个PassRefPtr转移给一个裸指针。
总之,adoptRef就是把一个继承自RefCounted的对象交给RefPtr管理。
我们看下面的例子
// example, not preferred style; should use RefCountedand adoptRef (see below) RefPtr<Node> createSpecialNode() { RefPtr<Node> a = new Node; a->setSpecial(true ); return a ; } RefPtr<Node> b = createSpecialNode();
为便于讨论,我们假定一个node对象开始时引用计数为0。当它被赋值给a时,引用计数增加到1。创建返回值时引用计数被增加到2,然后,当a被销毁时,引用计数被减少,变为1. 创建b时引用计数又增加到2,然后createSpecialNode的返回值被销毁 ,引用计数又减少到1。
如果编译器实现了返回值优化,引用计数增加和减少的次数可能会减少。
如果参数和返回值都是智能指针,引用计数附带的开销就更大了。解决这一个问题的方法是使用PassRefPtr。PassRefPtr与RefPtr有一点不同,当你拷贝一个PassRefPtr或者把一个PassRefPtr的值赋给一个RefPtr或另一个PassRefPtr时,原来的指针值被设置为0; 该操作不会改变引用计数的值。
我们看看PassPtrRef与传值有关的实现:
PassRefPtr(const PassRefPtr& o) : m_ptr(o.leakRef()){ } template<typename U> PassRefPtr(constPassRefPtr<U>& o) : m_ptr(o.leakRef()) { } template<typename T> inline T*PassRefPtr<T>::leakRef() const { T* ptr = m_ptr; m_ptr =nullptr; return ptr; }
leakRef就是把管理的指针转移给值的接收者,不涉及到引用计数的操作。要记住一点,一个作为右值的PassRefPtr对象是不能再使用的。因此,应确保只在函数参数和返回类型中使用PassRefPtr。
PassRefPtr的存在就是为了减少在参数传递和函数返回时因使用RefPtr而导致的引用计数操作。
用一个PassRefPtr初始化RefPtr,或者赋值给RefPtr后,原来的PassRefPtr不能再使用
template<typename T> template<typename U>inline RefPtr<T>::RefPtr(const PassRefPtr<U>& o) :m_ptr(o.leakRef()) { }
用一个RefPtr初始化PassRefPtr时,不涉及到引用计数操作。
template<typename T> template<typename U> inlinePassRefPtr<T>::PassRefPtr(const RefPtr<U>& o) :m_ptr(o.get()) { T* ptr = m_ptr; refIfNotNull(ptr); }
因此,要确保在PassRefPtr使用之前不释放RefPtr管理的指针。遵循如下使用原则就不会用问题:
只在函数参数和返回类型中使用PassRefPtr,并把函数参数拷贝到一个RefPtr中使用。
它们之间可以相互转化,但实际上没这个必要,可以像使用裸指针一样直接使用RefPtr, PassRefPtr,因为重载了操作符 “*”, “->”
T& operator*() const { return *m_ptr; } ALWAYS_INLINE T* operator->() const { return m_ptr; }
如果能够确定所有权和生命周期,一个本地变量可以是裸指针。
如果不能确定,又需要保证所有权或声明周期,那应该使用RefPtr。
本地变量绝不应该是一个PassRefPtr
如果能够确定所有权和生命周期,一个数据成员可以是裸指针。
如果不能确定,又需要保证所有权或声明周期,那应该使用RefPtr。
数据成员绝不应该是一个PassRefPtr
如果一个函数不占有一个对象,就应该使用裸指针作为参数。
如果一个函数需要占有一个对象,则应该使用PassRefPtr。大多数的setter函数是这样子的。参数应该在函数一开始的时候传递给一个RefPtr,除非对参数的使用非常简单。可以一个”prp”前缀来给参数命名。
如果函数返回一个对象,但并不转移它的所有权,则返回值应该是个裸指针。比如大部分的getter函数。
如果函数返回值是一个new对象或者需要转移所有权,则返回值应该使用PassRefPtr。本地变量通常是一个RefPtr,所以在返回语句中经常调用release,以把一个RefPtr转移给一个PassRefPtr。
PassRefPtr<T> release() { PassRefPtr<T> tmp =adoptRef(m_ptr); m_ptr = nullptr; return tmp; }
任何时候,new对象都应该立即放入RefPtr中,以允许智能指针自动完成所有的引用计数操作。
对于RefCounted对象,应该通过adoptRef函数来完成上述操作。
对于非RefCounted对象,最佳实践是使用一个private构造函数和一个返回一个PassRefPtr的public create函数。
class Item { public: PassRefPtr<Item>CreateItem() { } private: Item(){} }; PassRefPtr<Item> CreateItem() { RefPtr<Item>a = new Item; return a.release(); }