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

时间: 2024-12-20 03:07:29

webkit智能指针 - RefPtr, PassRefPtr的相关文章

Chromium和WebKit的智能指针实现原理分析

C++不像Java一样,由虚拟机负责对象分配和释放.也就是说,开发人员使用C++编写代码时,要自己负责对象分配和释放.WebKit和Chromium都是使用C++开发的,因此它们也面临上述问题.在解决对象释放问题时,要做到在对象不需要时自动释放,因为手动释放会带来忘记释放或者释放后又继续使用的隐患.智能指针是实现对象自动释放的有效技术手段.本文就分析Chromium和WebKit的智能指针的实现. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! 在现实中,

COCOS2D-X中的智能指针

Cocos2d-x中所有内存管理方式的基础是引用计数,动态分配一个Ref对象后其引用计数为1,并通过retain和release来增持和减少其引用计数.引用计数本身并不能帮助我们进行内存管理. 为了正确地释放对象的内存,Cocos2d-x使用Objective-C里面的自动回收池的机制来管理对象内存的释放.Autorelease有点类似于一个共享的"智能指针",该"智能指针"的作用域为一帧,该帧结束后,它将释放自己的引用计数,此时,如果该对象没有被其他"共

cocos2d智能指针 转自:http://blog.csdn.net/nxshow/article/details/44699409

智能指针在C++11的标准中已经存在了, 分别是unique_ptr,shared_ptr,weak_ptr, 其中最常用的应该是share_ptr, 它采用引用计数的方式管理内存, 当引用计数为0的时候, 自动释放内存, 但是由于shared_ptr考虑到了线程安全, 所以会存在有较大的性能损失. 所以在实时游戏开发中, 往往不会用到shared_ptr. 在cocos2d-x3.2以及更高的版本中, cocos2d-x提供了自己的智能指针方案——RefPtr, 这套方案实际上也是模仿C++1

再谈智能指针

http://www.cnblogs.com/lewiskyo/p/4214592.html  之前写过一篇文章介绍智能指针,并且简单实现了auto_ptr. 里面提到 auto_ptr 不能做为Stl容器的元素,原因具体是 http://www.360doc.com/content/14/0429/16/1317564_373230218.shtml 简要来说就是在对Stl容器元素进行复制构造时,不能改变原来的值(const T2& value),而auto_ptr在复制构造的时候,一定会修改

智能指针的原理与设计

     智能指针:实际指行为类似于指针的类对象 ,它的一种通用实现方法是采用引用计数的方法.下面我们来看看智能指针实现的原理和方法: 1.智能指针将一个计数器与类指向的对象相关联,引用计数跟踪共有多少个类对象共享同一指针: 2.每次创建类的新对象时,初始化指针并将引用计数置为1: 3.当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数: 4.对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象

Cocos2d-x 3.2 智能指针

Cocos2d-x 3.2:智能指针 本文转载自深入理解Cocos2d-x 3.x:内置数据结构(1)智能指针 智能指针在 C++ 11 的标准中已经存在了,分别是unique_ptr,shared_ptr,weak_ptr,其中最常用的应该是share_ptr,它采用引用计数的方 式管理内存,当引用计数为0的时候,自动释放内存,但是由于shared_ptr考虑到了线程安全,所以会存在有较大的性能损失.所以在实时游戏开发中, 往往不会用到shared_ptr. 在Cocos2d-x 3.2以及更

指针辨析:悬垂指针、哑指针、野指针、智能指针

悬垂指针: 1:提出的原因: 请看下面的代码片段: [cpp] view plaincopyprint? int *p=NULL; void main() { int i=10;p=&i; cout<<"第一次:*p = "<<*p<<endl; cout<<"第二次:*p = "<<*p<<endl; } [cpp] view plaincopyprint? int *p=NULL;

C++智能指针的实现

说起智能指针,不少人都不陌生,比如auto_ptr.shared_ptr.unique_ptr.weak_ptr.根据shared_ptr的功能,自己仿造也实现了个. 对于shared_ptr这种智能指针,有一个共享的引用计数器来控制指针对象的销毁,当引用计数器变为0时,则销毁指针指向的对象.对于多线程安全问题,我在代码中使用的Interlocked系列的原子操作函数. 在学习过程中,渐渐学会了RAII(Resource Acquisition Is Initialization),慢慢领略到了

智能指针原理与简单实现

当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝:另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享. 智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count).智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针.    每次创建类的新对象时,初始化指针并将引用计数置为1:当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相