RefPtr and PassRefPtr Basics
来源:互联网 发布:java中list的用法 编辑:程序博客网 时间:2024/04/28 20:31
如转载,请注明出处!
原文地址:http://www.webkit.org/coding/RefPtr.html
History
在WebKit内核中,很多对象都有引用计数。操作引用计数的方法有两个,分别是用于增加引用计数的方法ref以及减少引用计数的方法deref。调用ref的次数与调用deref的次数必须相等。如果在对象的引用计数等于1时调用deref,那么对象就会被自动删除。类RefCounted提供了ref方法和deref方法的默认实现。如果某类c继承了RefCounted类,那么类c的对象就有引用计数。2005年的时候,我们发现有很多内存泄漏,在实现HTML编辑功能的代码中内存泄漏更为严重。内存泄漏的原因是错误地使用了ref方法和deref方法。我们想要使用智能指针来减少内存泄漏量。我们做了一些试验,发现智能指针对引用计数的操作耗时较大,严重影响了性能。例如,一个函数使用智能指针作为参数,返回同一类型的智能指针作为返回值。每产生一个临时智能指针,就会对引用计数操作2次。创建智能引用计数时,引用计数会增1。之后目标智能指针销毁时,会再将引用计数减1。传递实参、返回运算结果至少创建1个临时智能指针,最多创建2个临时智能指针。因此,这个过程会操作引用计数2到4次。我们想要找到一种减少引用次数操作次数的方法。
C++标准库中的类auto_ptr给我们带来了启发。auto_ptr拥有一个对象的所有权。如果auto_ptr对象做赋值运算,那么源auto_ptr对象将不再持有对象所有权,目标auto_ptr对象持有对象的所有权。
Maciej Stachowiak设计了两个模板类RefPtr、PassRefPtr。这两个类应用了传递所有权的思想。WebCore模块使用这两个类来管理引用计数。
raw pointer
在讨论智能指针类型RefPtr模板类时,我们使用词组raw pointer来表示c++内嵌的指针类型。raw pointer的设置方法具有固定样式的,如下代码所示:
// example, not preferred styleclass 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;}
RefPtr
RefPtr是一个简单的智能指针。在获取对象指针后执行对象的ref方法,在失去对象指针前执行对象的deref方法。只要对象具有成员方法ref和deref,那么RefPtr就可以持有该对象的指针。下面是设置RefPtr智能指针的示例代码:
// example, not preferred style class Document { ... RefPtr<Title> m_title;}void Document::setTitle(Title* title){ m_title = title;}
仅仅使用RefPtr会导致引用计数从固定值v先增加1、再减小1、再增加1、再减1,重新变为v。后文中,将这种现象称为引用计数波动。如下示例代码:
// example, not preferred style; should use RefCounted and adoptRef (see below) RefPtr<Node> createSpecialNode(){ RefPtr<Node> a = new Node; a->setSpecial(true); return a;}RefPtr<Node> b = createSpecialNode();
在分析上段代码之前,先假设新建Node对象的引用计数为0。Node对象的引用计数变化为:
- 新建Node对象的引用计数为0。
- 将新建Node对象负值给a后,Node对象的引用计数变为1。
- 创建返回值后,Node对象的引用计数变为2。
- 销毁临时变量a后,Node对象的引用计数变为1。
- 把返回值赋值给b后,Node对象的引用计数变为2。
- 返回值对象销毁后,Node对象的引用计数变为1。
需要额外说明的是,如果编译器实现了return value optimization,那么可以减少返回值对象的创建,也就将引用计数加1、减1的次数各减去1次。
如果方法参数类型是RefPtr,方法返回值类型也是RefPtr,那么引用计数波动带来的额外耗时更大。PassRefPtr可以减小引用计数波动。
如果方法参数类型是RefPtr,方法返回值类型也是RefPtr,那么引用计数波动带来的额外耗时更大。PassRefPtr可以减小引用计数波动。
PassRefPtr
PassRefPtr与RefPtr类似,但不完全相同。将PassRefPtr赋值给另一个PassRefPtr,或者将PassRefPtr赋值给RefPtr,并不会改变对象的引用计数。还是假设新建对象的引用计数为0。如下示例代码:
// example, not preferred style; should use RefCounted and adoptRef (see below)PassRefPtr<Node> createSpecialNode(){ PassRefPtr<Node> a = new Node; a->setSpecial(true); return a;}RefPtr<Node> b = createSpecialNode();
代码运行过程中,Node引用计数的变换过程如下:
- 新建Node的引用计数为0。
- 把Node对象赋值给a后,引用计数变为1。
- 返回值对象创建后,a不再持有Node对象的指针,引用计数仍然为1。
- 返回值对象赋值给b后,返回值不再持有Node对象的指针,引用计数仍然为1。
// warning, will dereference a null pointer and will not work static RefPtr<Ring> g_oneRingToRuleThemAll;void finish(PassRefPtr<Ring> ring){ g_oneRingToRuleThemAll = ring; ... ring->wear();}
在执行ring->wear()方法时,ring已经不持有任何对象了,ring相当于0。为了避免出现这样的问题,我们强烈建议只将PassRefPtr用于参数和返回值。在使用智能指针之前,必须将PassRefPtr赋值给RefPtr类型局部变量。如下示例代码:
static RefPtr<Ring> g_oneRingToRuleThemAll;void finish(PassRefPtr<Ring> prpRing){ RefPtr<Ring> ring = prpRing; g_oneRingToRuleThemAll = ring; ... ring->wear();}
Mixing RefPtr and PassRefPtr
PassRefPtr的复制、赋值都会把对象所有权传递出去。PassRefPtr作为参数和返回值,可以减少引用计数波动。我们建议在函数体内尽量使用RefPtr来操作对象。函数体内也会遇到直接传递对象所有权的需求。RefPtr提供了一个成员方法release用于传递对象所有权。release方法会先将自己持有的raw pointer备份到一个局部变量ptr,然后将自己的raw pointer设置为0,最后创建一个PassRefPtr拥有ptr的所有权。这个过程不会改变ptr对象的引用计数。如下示例代码:// example, not preferred style; should use RefCounted and adoptRef (see below) PassRefPtr<Node> createSpecialNode(){ RefPtr<Node> a = new Node; a->setCreated(true); return a.release();}RefPtr<Node> b = createSpecialNode();
不使用RefPtr的release方法,需要编译器自己将return 语句转换为return PassRefPtr(RefPtr)类型的代码。这个过程依赖于编译器的实现。如果编译器实现的语义功能有缺陷,那么就无法实现减小引用计数波动的目标。使用RefPtr的release方法可以在减少引用计数波动的同时,降低编译器语义错误发生的概率。
Mixing with raw pointers
如果某个方法需要raw pointer作为参数,那么可以使用RefPtr成员方法get获得raw pointer。如下示例代码:printNode(stderr, a.get());
RefPtr(PassRefPtr)类重载了许多操作符。有些情况下,可以直接使用RefPtr(PassRefPtr)。如下示例代码:
RefPtr<Node> a = createSpecialNode();Node* b = getOrdinaryNode();// the * operator*a = value;// the -> operatora->clear();// null check in an if statementif (a) log("not empty");// the ! operatorif (!a) log("empty");// the == and != operators, mixing with raw pointersif (a == b) log("equal");if (a != b) log("not equal");// some type castsRefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);
Reftr和PassRef会自动调用对象的ref方法以及deref方法,以保证ref方法调用次数和deref调用次数肯定相等。这样可以减小程序员的工作复杂度,降低内存泄漏发生的概率。有一个问题必须特殊处理,即如果对象raw pointer的引用计数大于0,那么如何将这个对象的所有权传递给RefPtr和PassRefPtr呢?赋值语句以及拷贝构造函数会增加这个对象的引用计数,这不符合“传递所有权不改变引用计数”这条规则。WebKit内核提供了一个全局函数adpotRef来解决这个问题。如下示例代码:
// warning, requires a pointer that already has a refRefPtr<Node> node = adoptRef(rawNodePointer);
另外,WebKit内核也支持反过程。即,不改变引用计数的同时将对象指针赋值给raw pointer。方法是执行PassRefPtr的成员方法leakRef。如下示例代码:
// warning, results in a pointer that must get an explicit derefRefPtr<Node> node = createSpecialNode();Node* rawNodePointer = node.release().leakRef();
因为leakRef的应用场景比较少,所以只有PassRefPtr提供了这个方法。可以先通过release方法获得PassRefPtr,在执行leakRef方法。
RefPtr and new objects
在前述小节的讨论中,一直假设新建对象的引用计数是0。但实际上,新建RefCounted对象的引用计数是1。在执行deref时,发现对象引用计数已经是1了,那么就会直接删除对象。也就是说,对象的引用计数永远不会为0。这样做的好处很明显,减少引用计数额外的操作可以提高速度。为了防止内存泄漏,最好使用RefPtr智能指针来管理对象。这样可以防止出现“忘记执行deref”的情况。这也意味着一旦执行完new操作,必须立刻执行adoptRef方法。在WebCore模块,我们会执行类方法create来创建对象,而不是直接调用new运算符。如下示例代码:
// preferred style PassRefPtr<Node> Node::create(){ return adoptRef(new Node);}RefPtr<Node> e = Node::create();
adoptRef和PassRefPtr都不会改变对象的引用计数。因此,采用示例代码中create的实现方式,可以保证只在创建对象时对引用计数做了赋值操作,其余情况都不会改变引用计数的值。提高了代码的运行效率。
RefCounted加了一个运行时检查的逻辑。如果在执行ref和deref方法之前,没有执行adoptRef,那么一个assert断言就会失败。
GuideLines
我们总结了一些RefPtr和PassRefPtr的使用原则。
1. 局部变量
1. 局部变量
- 如果当前作用域既不拥有对象所有权也不需要管理对象生命周期,那么可以直接使用raw pointer。
- 如果当前作用域既拥有对象所有权也需要管理对象生命周期,那么需要使用RefPtr。
- 永远不要使用PassRefPtr作为局部变量。
- 如果当前类既不拥有对象所有权也不需要管理对象生命周期,那么可以直接使用raw pointer。
- 如果当前类既拥有对象所有权也需要管理对象生命周期,那么需要使用RefPtr。
- 永远不要使用PassRefPtr作为成员变量。
- 如果函数既不拥有对象所有权,那么可以使用raw pointer。
- 如果函数拥有对象所有权,应该使用PassRefPtr。应该在函数的起始位置就将PassRefPtr赋值给RefPtr类型的局部变量,局部变量最好具有前缀”prp”。这种情况常见于setter方法中。
- 如果函数返回值是一个已存在的对象,且不需要传递对象所有权,那么使用raw pointer。这种情况常见于getter方法中。
- 如果函数返回值是一个新建对象,或者需要传递对象所有权,那么应该使用PassRefPtr。因为局部变量是RefPtr类型,所以建议将RefPtr的release方法返回值放在return语句中。
- 今早将新建对象所有权传递给智能指针RefPtr。
- 如果新建对象是RefCounted类型,那么使用adoptRef来传递对象所有权。
- 建议将对象的构造方法设置为私有的。另外,提供类方法create。create方法的返回值是PassRefPtr。
- RefPtr and PassRefPtr Basics
- RefPtr and PassRefPtr Basics
- RefPtr and PassRefPtr Basics
- Refptr and PassRefPtr basic
- 学习webkit中 RefPtr PassRefPtr Basics
- Webkit RefPtr and PassRefPtr Basic
- Refptr and PassRefPtr basic(adopref)
- RefPtr and PassRefPtr基础 -- WebKit中的引用计数
- WebKit中的RefPtr和PassRefPtr
- Ref, RefPtr, PassRefPtr" study note
- [WebKit]RefPtr和PassRefPtr基础
- [WebKit]RefPtr和PassRefPtr基础
- webkit智能指针 - RefPtr, PassRefPtr
- [WebKit]RefPtr和PassRefPtr基础
- RefPtr和PassRefPtr基础[1]
- [WebKit]RefPtr和PassRefPtr基础[1]
- [WebKit]RefPtr和PassRefPtr基础[1]
- [WebKit]RefPtr和PassRefPtr基础[1]
- Boost 库
- 伤感语录:浅唱,没人懂的悲伤
- TP-设置WDS
- 积分图
- Android入门之TabHost一(不用xml,纯java)
- RefPtr and PassRefPtr Basics
- hive优化的几篇博文
- 谁,又把青春消耗在暗恋里了呢?伤感的QQ空间日志
- 随便举个例子,说明单线程比多线程编程提高性能
- 编程方法
- boost库的常用组件的使用
- 30 分钟快快乐乐学 SQL Performance Tuning
- 存储设备分区操作及文件系统管理概述
- 向中级程序员转变必备的10个秘诀