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

相关文章

来自专栏机器学习算法与Python学习

python基础语法(1)

从今天起,将进行python的一个系列学习,从基本的语法学起,后期会推出一些关于web开发,网络爬虫以及用python的第三方库进行数据挖掘与机器学习等高级的开...

34814
来自专栏CodeSheep的技术分享

Java编程思想学习录(连载之:内部类)

19611
来自专栏用户2442861的专栏

JavaScript 正则表达式上——基本语法

JavaScript种正则表达式有两种定义方式,定义一个匹配类似 <%XXX%> 的字符串

341
来自专栏GreenLeaves

JavaScript之对象学习

对象是一种非常重要的数据类型,他是一种自包含的数据集合,包含在对象里面的数据可以通过属性和方法两种形式来访问; 1.属性是隶属于某个特定对象的变量; 2.方法是...

1817
来自专栏大数据学习笔记

Java程序设计(Java9版):第2章 数据类型与运算符(Data types and Operators)

第2章 数据类型与运算符(Data types and Operators) I think everybody in this country should ...

1845
来自专栏野路子程序员

【野路子】正则表达式~极速入门图文教程

3048
来自专栏龙首琴剑庐

Java总论及三大特性理解

1、对象(object)     万物皆为对象(根类Object类)。     程序是对象的集合(面向对象程序设计语言OOP)。     每个对象都有自己的由其...

2916
来自专栏醒者呆

正则表达式——Java程序员懂你

正则表达式 关键字:正则表达式,Pattern,Matcher,字符串方法,split,replace 前文书立下了一个flag,这里要把它完成,就是正则...

3395
来自专栏积累沉淀

Java设计模式(十四)----模板方法模式

模板方法模式 一、概述 二、结构 三、具体案例 四、优缺点和设计思想 一、概述 模板方法模式是类的行为模...

1875
来自专栏null的专栏

python基础知识——基本语法

在python基础知识这个系列中,准备罗列出我学习python的一些基础知识,包括: 基本语法 控制语句 内置数据结构 模块和函数 字符串 文件处理 面向对象 ...

3518

扫描关注云+社区