webkit智能指针 - RefPtr, PassRefPtr

历史

          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

          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管理。


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。

PassPtrRef

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

用一个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

它们之间可以相互转化,但实际上没这个必要,可以像使用裸指针一样直接使用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();
}
 

参考:http://blog.csdn.net/wy5761/article/details/20654275



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。