Webkit中的很多对象都是引用计数的,所使用的方法是在类中加入ref和deref成员函数用于增加和减小引用计数。调用ref的次数一定要和调用deref的次数相同,当引用计数deref到1的时候,该对象会被释放。Webkit中的很多类通过继承RefCounted类模板来实现这种方法。
2005年,我们发现有很多的内存泄露,特别是在HTML editing code中误用了ref和deref调用的时候。
我们想用智能指针来解决这个问题。但是,一些验证性试验表明智能指针会使用额外的引用计数操作,会导致性能降低。例如,如果一个函数使用了智能指针作为参数,并且返回同样的智能指针作为返回值,单就是作为参数进行传递和返回该值,由于对象在智能指针之间传递,就会导致增加或者减少引用计数两到四次。所以我们在寻找一种理想的方式,能让我们使用智能指针,并且避免大量使用引用计数。
一个解决方法的灵感来自于C++标准类模板auto_ptr。使用auto_ptr的对象之间在赋值的时候,它的所有者也发生了改变,赋值者的引用数会变为0,对象归被赋值者所有。
Maciej Stachowiak设计了一对类模板,RefPtr和PassRefPtr,实现了这种方案,来解决WebCore的引用计数问题。
原始指针(Rawpointer)
当讨论类似RefPtr类模板这样的智能指针的时候,我们使用了原始指针这个术语来表示C++语言的内建指针类型。这里给出了使用原始指针实现的规范的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* title)
{
if (title)
title->ref();
if (m_title)
m_title->deref();
m_title = title;
}
RefPtr
RefPtr是一个简单的智能指针类,它调用ref来增加引用,调用deref来减小引用。RefPtr在任何有ref和deref成员函数的对象上都会起作用,下面是用RefPtr写的setter例子
// example, not preferred style
class Document {
...
RefPtr
}
void Document::setTitle(Title* title)
{
m_title = title;
}
如果单独使用RefPtr也会导致引用计数的泛滥
RefPtr
{
RefPtr
a->setSpecial(true);
return a;
}
RefPtr
我们假设node对象的引用计数从0开始,当它赋值给a的时候,引用计数增加了1(引用operator=被RefPtr重载了),从函数返回的时候,因为a实际上只是一个临时变量,编译器会把它复制给一个临时变量作为返回值,此时发生了赋值操作,导致引用计数变为2,同时由于a是临时变量,所以在a被销毁的时候,析构函数被调用,导致引用计数变为1。作为返回值的临时变量把值赋值给变量b,此时引用计数又会增加为2,在临时变量销毁的时候,引用计数又变为1。
(如果编译器进行了返回值优化的话,会减少一次引用计数的增加减少过程)
如果函数的参数和返回值都涉及了智能指针的使用,那么引用计数导致的overhead会更多。
解决方案就是PassRefPtr
PassRefPtr
PassRefPtr与RefPtr类似,但是有一点不同,当你复制一个PassRefPtr或者把一个PassRefPtr赋值给一个RefPtr或者另外一个PassRefPtr的时候,PassRefPtr中的原始指针会被设置为0;该操作不会导致引用计数有任何的变化。下面是用PassRefPtr的例子
PassRefPtr
{
PassRefPtr
a->setSpecial(true);
return a;
}
RefPtr
假设Node对象起始引用计数为0,当它赋值给a的时候,引用计数增加为1。在函数返回的时候,变量a赋值给返回结果临时变量的时候,a中的引用被设置为0,在返回结果临时变量赋值给变量b的时候,b中的引用被设置为0。
注:PassRefPtr通过在赋值的时候,传递指针而不是增加减少引用计数的方式,降低了引用计数泛滥导致的性能降低问题。
但是,Safari team在实践中发现,PassRefPtr这种方式很容易出现问题。
static RefPtr
void finish(PassRefPtr
{
g_oneRingToRuleThemAll = ring;
...
ring->wear();
}
在ring->wear()的时候,ring中包含的指针已经被设置为0.为了避免发生这种情况,我们建议只在函数参数的返回值的时候,使用PassRefPtr,如果要使用其中的变量,要把它赋值给一个RefPtr本地变量
static RefPtr
void finish(PassRefPtr
{
RefPtr
g_oneRingToRuleThemAll = ring;
...
ring->wear();
}
混合使用RefPtr和PassRefPtr
我们建议,在作为参数进行传递和函数返回值的情况下使用PassRefPtr,其他情况下使用RefPtr,但是也有很多时候,你会希望像PassRefPtr那样,转移RefPtr的所有权。RefPtr有一个名为release的成员函数实现着这功能。它设置RefPtr的值为0,并且构建新的PassRefPtr而不改变引用计数。
PassRefPtr
{
RefPtr
a->setCreated(true);
return a.release();//这里返回PassRefPtr,并且把a中的引用设置为0
}
RefPtr
这既保证了PassRefPtr的效率,又降低了技巧性的语义导致问题的机会。
与原始指针一起使用
如果一个函数的参数需要使用原始指针,那么可以使用RefPtr的get函数
printNode(stderr, a.get());
但是很多操作可以直接使用RefPtr或者PassRefPtr,而不用显式的调用get函数
RefPtr
Node*