【专业知识】 Webkit智能指针用法

历史:

在WebKit中,许多对象采用了引用计数。这种模式是通过类的ref,deref成员函数来递增和递减对象的引用记数。调用一次ref必须调用一次deref。当对象的引用记数为0的时候,对象就被删除。WebKit中许多类创建的新对象引用记数都为0,这被称作是浮动状态(Floating State)。在浮动状态的对象必须调用ref,在删除之前必须调用deref。WebCore中许多类通过继承RefCounted模版类来实现这种模式。

在2005年的时候,我们发现存在很多内存泄漏的问题,特别实在WebCore编辑器代码中,这主要是由没有正确的使用ref和deref调用,还有就是创建的对象没有调用ref,依然是浮动状态。

我们决定我们使用智能指针来解决这个问题,然而,一些前期的实验表明,智能指针导致引用记数的其他操纵影响性能。例如,一个函数使用智能指针来传递参数,函数返回时也使用这个智能指针作为返回值,仅仅在一个对象从一个智能指针移动到另外一个时,传递参数和返回函数值时就递增和递减引用记数2-4次。因此,我们寻求一种能够让我们使用智能指针又避免使用这种引用记数的性能流失的方法。

这种解决方案的灵感来源于C++的标准模版类auto_ptr。应用这种模式的对象在赋值的时候将传递了所有权。当你把一个auto_ptr传递给另外一个时,传递者变为0。

Maciej Stachowiak设计了一对模版类,RefPtr和PassRefPtr,它为WebCore的引用记数实现了这种模式。 原始指针:

在讨论如RefPtr模版类这类智能指针时,我们使用原始指针来构建,下面是使用原始指针写的规范的Setter函数。

  1. // example, not preferred style
  2. class Document {
  3. [...]
  4. Title* m_title;
  5. }
  6. Document::Document(): m_title(0)
  7. {
  8. }
  9. Document::~Document()
  10. {
  11. if (m_title)
  12. m_title->deref();
  13. }
  14. void Document::setTitle(Title* t)
  15. {
  16. if (t)
  17. t->ref();
  18. if (m_title)
  19. m_title->deref();
  20. m_title = t;
  21. }

RefPtr RefPtr是一种简单的智能指针,主要是通过在传入值时调用ref,传出时调用deref。RefPtr工作在有ref和deref成员函数的对象上。下面是一个例子:

  1. // example, not preferred style
  2. class Document {
  3. [...]
  4. RefPtr<Title> m_title;
  5. }
  6. void Document::setTitle(Title* t)
  7. {
  8. m_title = t;
  9. }

单独使用RefPtr会导致引用记数的流失,例如下面的例子:

  1. // example, not preferred style
  2. RefPtr<Node> createSpecialNode()
  3. {
  4. RefPtr<Node> a = new Node;
  5. a->setSpecial(true);
  6. return a;
  7. }
  8. RefPtr<Node> b = createSpecialNode();

从这个方面来说,假定Node对象开始时引用记数为0,当它被赋值到a时,递增引用记数为1。在创建返回值时递增引用记数到2,当a销毁的时候递减回1.然后在创建b的时候引用记数递增到2,在createSpecialNode函数的返回值销毁时递减到1.(这些分析忽略了编译器返回值优化的可能性,如果编译器这么做了,可能导致引用记数的流失) 当涉及到函数参数和返回值时,引用记数的流失的代价比较大,解决方法就是PassRefPtr。

PassRefPtr PassRefPtr除过有一点区别其他都和RefPtr类似,当传递一个PassRefPtr,或者把PassRefPtr赋值到RefPtr或者另一PassRefPtr时,原始的指针值设置为0;操作没有做任何对引用记数的更改。下面看一个新的例子:

  1. // example, not preferred style
  2. PassRefPtr<Node> createSpecialNode()
  3. {
  4. PassRefPtr<Node> a = new Node;
  5. a->setSpecial(true);
  6. return a;
  7. }
  8. RefPtr<Node> b = createSpecialNode();

Node对象开始时,引用记数为0,当被赋值到a时,引用记数加1,在返回值PassRefPtr创建时,a被设置为0,当创建b时,返回值设置为0。 然后,当我们开始使用PassRefPtr编码时,Safari团队发现当被赋值到另一个变量时,指针变为了0,这种很容易导致错误。

  1. // example, not preferred style
  2. static RefPtr<Ring> g_oneRingToRuleThemAll;
  3. void finish(PassRefPtr<Ring> ring)
  4. {
  5. g_oneRingToRuleThemAll = ring;
  6. ...
  7. ring->wear();
  8. }

在wear被调用时,ring已经是空指针了,为了避免这种情况,建议PassRefPtr只在作为函数参数,返回值和拷贝参数到RefPtr局部变量时使用。

  1. static RefPtr<Ring> g_oneRingToRuleThemAll;
  2. void finish(PassRefPtr<Ring> prpRing)
  3. {
  4. RefPtr<Ring> ring = prpRing;
  5. g_oneRingToRuleThemAll = ring;
  6. ...
  7. ring->wear();
  8. }

混合使用RefPtr和PassRefPtr

除过当传递参数或者函数返回值时这些情况外,建议使用RefPtr。,在打算把RefPtr所有权转移到PassRefPtr时,RefPtr类有一个release成员函数,它能够设置RefPtr到0,然后构建一个PassRefPtr对象,这期间没有改变引用记数。

  1. // example, assuming new Node starts with reference count 0
  2. PassRefPtr<Node> createSpecialNode()
  3. {
  4. RefPtr<Node> a = new Node;
  5. a->setCreated(true);
  6. return a.release();
  7. }
  8. RefPtr<Node> b = createSpecialNode();

这种方式保持了PassRefPtr的有效性,同时也降低了导致问题的可能性。

与原始指针混合使用

RefPtr使用get方法来获得一个原始指针

  1. printNode(stderr, a.get());

然而,这些操作可以不使用get调用,而通过RefPtr和PassRefPtr直接完成。

  1. RefPtr<Node> a = createSpecialNode();
  2. Node* b = getOrdinaryNode();
  3. // the * operator
  4. *a = value;
  5. // the -> operator
  6. a->clear();
  7. // null check in an if statement
  8. if (a)
  9. log("not empty");
  10. // the ! operator
  11. if (!a)
  12. log("empty");
  13. // the == and != operators, mixing with raw pointers
  14. if (a == b)
  15. log("equal");
  16. if (a != b)
  17. log("not equal");
  18. // some type casts
  19. RefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);

[参考]http://webkit.org/coding/RefPtr.html

转:http://blog.csdn.net/cnwarden/article/details/4628049

原文发布于微信公众号 - 程序员互动联盟(coder_online)

原文发表时间:2015-06-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏逸鹏说道

经典JS闭包面试题

大部分人都会做错的经典JS闭包面试题 目录 由工作中演变而来的面试题 JS中有几种函数 创建函数的几种方式 三个fun函数的关系是什么? 函数作用域链的问题 到...

2895
来自专栏性能与架构

Mysql小细节:varchar与char在性能上的特点

varchar与char的一个主要区别是存储方式的不同 varchar 是变长存储 占用的存储空间 = 存储内容实际大小 + 长度记录位 ? char 是定长存...

34910
来自专栏PHP技术

字符串和编码

字符编码 我们已经讲过了,字符串也是一种数据类型,但是,字符串比较特殊的是还有一个编码问题。 因为计算机只能处理数字,如果要处理文本,就必须先把文本转换为数字才...

2687
来自专栏陈纪庚

不到50行代码实现一个能对请求并发数做限制的通用RequestDecorator

在开发中,我们可能会遇到一些对异步请求数做并发量限制的场景,比如说微信小程序的request并发最多为5个,又或者我们需要做一些批量处理的工作,可是我们又不想同...

652
来自专栏司想君

你不知道的Javascript:有趣的setTimeout

有时候,小小的细节往往隐藏着大大的智慧 今天在回顾JavaScript进阶用法的时候,发现一个有趣的问题,话不多说,先上代码: for(var j=0;j<10...

2664
来自专栏老司机的技术博客

人人都能学会的python编程教程3:字符串和编码

由于Python源代码也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。当Python解释器读取源代码时...

4378
来自专栏数说工作室

统计师的Python日记【第1天:谁来给我讲讲Python?】

统计师的Python日记 【第一天】谁来给我讲讲Python? 我是一名数据分析师,曾在漫长的岁月中使用SAS、Matlab和R(使用频率依次递减)。其他如...

3696
来自专栏PHP技术

PHP知识点

JSON支持 包括json_encode(), json_decode()等函数,JSON算是在web领域非常实用的数据交换格式,可以被JS直接支持,JSON实...

3306
来自专栏HappenLee的技术杂谈

C++雾中风景番外篇:理解C++的复杂声明与声明解析

在编写C/C++代码时偶尔能看到如下的复杂声明:float(*(*e[10])(int*))[5]。我想你的第一反应一定是:MMP。虽然我们在实际工作之中是很少...

692
来自专栏十月梦想

异步函数async和await

前面我们介绍的是promise对象,这里我们介绍一下async...await异步函数,创建函数时候使用async关键词表示这是一个异步函数,await必须和a...

612

扫码关注云+社区