历史:
在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函数。
- // example, not preferred style
- class Document {
- [...]
- Title* m_title;
- }
- Document::Document(): m_title(0)
- {
- }
- Document::~Document()
- {
- if (m_title)
- m_title->deref();
- }
- void Document::setTitle(Title* t)
- {
- if (t)
- t->ref();
- if (m_title)
- m_title->deref();
- m_title = t;
- }
RefPtr
RefPtr是一种简单的智能指针,主要是通过在传入值时调用ref,传出时调用deref。RefPtr工作在有ref和deref成员函数的对象上。下面是一个例子:
- // example, not preferred style
- class Document {
- [...]
- RefPtr<Title> m_title;
- }
- void Document::setTitle(Title* t)
- {
- m_title = t;
- }
单独使用RefPtr会导致引用记数的流失,例如下面的例子:
- // example, not preferred style
- 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。
PassRefPtr
PassRefPtr除过有一点区别其他都和RefPtr类似,当传递一个PassRefPtr,或者把PassRefPtr赋值到RefPtr或者另一PassRefPtr时,原始的指针值设置为0;操作没有做任何对引用记数的更改。下面看一个新的例子:
- // example, not preferred style
- PassRefPtr<Node> createSpecialNode()
- {
- PassRefPtr<Node> a = new Node;
- a->setSpecial(true);
- return a;
- }
- RefPtr<Node> b = createSpecialNode();
Node对象开始时,引用记数为0,当被赋值到a时,引用记数加1,在返回值PassRefPtr创建时,a被设置为0,当创建b时,返回值设置为0。
然后,当我们开始使用PassRefPtr编码时,Safari团队发现当被赋值到另一个变量时,指针变为了0,这种很容易导致错误。
- // example, not preferred style
- static RefPtr<Ring> g_oneRingToRuleThemAll;
- void finish(PassRefPtr<Ring> ring)
- {
- g_oneRingToRuleThemAll = ring;
- ...
- ring->wear();
- }
在wear被调用时,ring已经是空指针了,为了避免这种情况,建议PassRefPtr只在作为函数参数,返回值和拷贝参数到RefPtr局部变量时使用。
- static RefPtr<Ring> g_oneRingToRuleThemAll;
- void finish(PassRefPtr<Ring> prpRing)
- {
- RefPtr<Ring> ring = prpRing;
- g_oneRingToRuleThemAll = ring;
- ...
- ring->wear();
- }
混合使用RefPtr和PassRefPtr
除过当传递参数或者函数返回值时这些情况外,建议使用RefPtr。,在打算把RefPtr所有权转移到PassRefPtr时,RefPtr类有一个release成员函数,它能够设置RefPtr到0,然后构建一个PassRefPtr对象,这期间没有改变引用记数。
- // example, assuming new Node starts with reference count 0
- PassRefPtr<Node> createSpecialNode()
- {
- RefPtr<Node> a = new Node;
- a->setCreated(true);
- return a.release();
- }
- RefPtr<Node> b = createSpecialNode();
这种方式保持了PassRefPtr的有效性,同时也降低了导致问题的可能性。
与原始指针混合使用
RefPtr使用get方法来获得一个原始指针
- printNode(stderr, a.get());
然而,这些操作可以不使用get调用,而通过RefPtr和PassRefPtr直接完成。
- RefPtr<Node> a = createSpecialNode();
- Node* b = getOrdinaryNode();
- // the * operator
- *a = value;
- // the -> operator
- a->clear();
- // null check in an if statement
- if (a)
- log("not empty");
- // the ! operator
- if (!a)
- log("empty");
- // the == and != operators, mixing with raw pointers
- if (a == b)
- log("equal");
- if (a != b)
- log("not equal");
- // some type casts
- RefPtr<DerivedNode> d = static_pointer_cast<DerivedNode>(a);
[参考]http://webkit.org/coding/RefPtr.html