【专业知识】 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 条评论
登录 后参与评论

相关文章

来自专栏信数据得永生

JavaScript 编程精解 中文第三版 三、函数

3347
来自专栏企鹅号快讯

Python入门基础连载(1)数据类型

Python入门很简单,应该说语法还是很简单明了,有一定C或者java或者别的语言基础的亲们都能明白。大数据,机器学习大势所趋,乘着这风,大家一起学习Pytho...

2016
来自专栏Crossin的编程教室

【Python 第52课】 元组

上一次pygame的课中有这样一行代码: x, y = pygame.mouse.get_pos() 这个函数返回的其实是一个“元组”,今天我们来讲讲这个东西。...

3407
来自专栏coding for love

JS入门难点解析9-闭包的深入解析

(注1:如果有问题欢迎留言探讨,一起学习!转载请注明出处,喜欢可以点个赞哦!) (注2:更多内容请查看我的目录。)

702
来自专栏编程

Python入门基础连载(2)数据结构

Python数据结构包括了列表(list),元组(tuple),字典(dict)和集合(set),这些也都可以称之为容器,下面Cooldog就和大家一起学习一下...

2057
来自专栏Python入门

十年Python大牛花了三天总结出来的python基础知识实例,超详细!

1843
来自专栏陈纪庚

javascript变量提升详解

对于大多数js开发者来说,变量提升可以说是一个非常常见的问题,但是可能很多人对其不是特别的了解。所以在此,我想来讲一讲。

1142
来自专栏python学习之旅

Python笔记(七):字典、类、属性、对象实例、继承

(一)  简单说明    字典是Python的内置数据结构,将数据与键关联(例如:姓名:张三,姓名是键,张三就是数据)。例如:下面这个就是一个字典 {'姓名':...

3875
来自专栏一名合格java开发的自我修养

java或判断优化小技巧

写业务代码的时候,我们经常要做条件判断,有的时候条件判断的或判断长达20多个。reg.equals("1") || reg.equals("2") || reg...

661
来自专栏zhisheng

干货分享:让你分分钟学会 javascript 闭包 一像素

闭包,是 javascript 中重要的一个概念,对于初学者来讲,闭包是一个特别抽象的概念,特别是ECMA规范给的定义,如果没有实战经验,你很难从定义去理解它。...

3515

扫码关注云+社区

领取腾讯云代金券