主要思路,对来值ref(refcount + 1),对去值deref(refcount - 1),这样不需手动做ref、deref操作就能合理的管理引用计数值。
以下是more effective c++中智能指针+引用计数的实现,它的组成为:
1. RCObject,引用计数类的基类,它封装了refcount +、- 操作ref()、deref()。无需其它额外操作。
2. RCPtr<class T>,模板类,它实现引用计数+智能指针的核心操作。持有RCObject指针,调用构造函数后对RCObject的refCount + 1,析构时对其 - 1。注意拷贝构造函数和operator=操作符的实现,来实现来值ref,去值deref操作。
3. 让类继承自RCObject,使用RCPtr<RCNormal>模板类型声明、创建对象。
//引用计数基类
class RCObject : public CBase
{
public:
RCObject() : refCount(0)/*, shareable(true)*/ {}
virtual ~RCObject() {}
/*重点处理拷贝构造和赋值操作
* 注意:两个操作都不要处理refCount值*/
RCObject(const RCObject& aObj) : refCount(0)/*, shareable(true)*/ {}
RCObject& operator=(const RCObject& aObj) { return *this; }
public:
//refcount操作
void ref()
{
++refCount;
#if DEBUG
LOGF8("refCount = [%d]", refCount);
#endif
}
void deref()
{
#if DEBUG
LOGF8("refCount = [%d]", refCount - 1);
#endif
if (--refCount == 0)
delete this;
}
int getRefCount() { return refCount; }
// //share操作,可有可无
// bool isShareable() { return shareable; }
// bool isShared() { return refCount > 1; }
// void markUnshareable() { shareable = false; }
private:
int refCount;
// bool shareable;
};
//智能指针
template <class T> class RCPtr
{
public:
//缺省构造函数
RCPtr(T* realPtr = 0) : pointee(realPtr) { init(); }
virtual ~RCPtr() { if (pointee) pointee->deref(); }
//拷贝构造
RCPtr(const RCPtr& rhs) : pointee(rhs.pointee)
{
#if DEBUG
_TRACE_;
#endif
init();
}
//=操作符
RCPtr& operator=(const RCPtr& rhs)
{
#if DEBUG
_TRACE_;
#endif
//这个判断条件一定不能忘
if (pointee != rhs.pointee)
{
if (pointee)
pointee->deref();
pointee = rhs.pointee;
init();
}
return *this;
}
public:
//重载指针操作用到的操作符
T* operator->() const { return pointee; }
T& operator*() const { return *pointee; }
private:
T* pointee;
//比较关键
void init()
{
if (!pointee)
return;
// if (pointee->isShareable() == false)
// pointee = new T(*pointee);
pointee->ref();
}
};
这里有几个值得注意的问题:
1. /* operator=的调用*/
RCPtr<Normal> objNull;
objNull = new Normal();
//以上调用产生LOG如下
//1. 先调用构造函数RCPtr(T* realPtr = 0),创建临时RCPtr<Normal>对象,refCount + 1
2012/02/13 08:43:58 refCount = [1]
//2. 调用operator=将临时对象,赋值给objNull
2012/02/13 08:43:58 RCPtr<Normal>::operator=(const RCPtr<Normal> &)
//3. refCount + 1
2012/02/13 08:43:58 refCount = [2]
//4. operator=调用完成
2012/02/13 08:43:58 END
//5. 析构临时对象refCount - 1
2012/02/13 08:43:58 refCount = [1]
可以看到这里因为临时对象的关系,reCount值有一次“跳跃”
2. 拷贝构造函数的调用
RCPtr<Normal> objNor(new Normal);
RCPtr<Normal> objNor1(objNor);
//以上调用产生LOG如下
//1. 先构造objNor对象,refCount + 1
2012/02/13 08:56:00 refCount = [1]
//2. 调用拷贝构造函数,创建objNor1对象
2012/02/13 08:56:00 RCPtr<Normal>::RCPtr(const RCPtr<Normal> &)
//3. refCount + 1
2012/02/13 08:56:00 refCount = [2]
//4. 拷贝构造函数调用完成
2012/02/13 08:56:00 END
//5. 析构临时对象refCount - 1
2012/02/13 08:56:00 refCount = [1]
2012/02/13 08:56:00 refCount = [0]
3. 做为函数参数值传递的情况
void passPtr(RCPtr<Normal> aNor)
{
aNor->getRefCount();
return;
}
RCPtr<Normal> b(new Normal);
/* 这里做了拷贝构造,把b拷贝给函数活动记录中的一个临时对象 */
passPtr(b);
4. 做为函数返回值的情况
RCPtr<Normal> createSpecialNode()
{
//正好检查一下RVO----直接优化掉了
RCPtr<Normal> a = new Normal;
a->getRefCount();
return a;
}
RCPtr<Normal> b = createSpecialNode();
理论上这里应该有很多因为临时对象而产生的操作的,但是因为目前很多编译器做了很好的编译器优化,这个问题就不是太突显了。
上面的代码在Symbian C++编译器下面试了下,优化的程度令我吃惊。。。上面各种调用,被编译优化为:
只调用一个拷贝构造函数!!!!实在不得了。
总结
经过几种情况的分析,我们应该注意使用 引用计数+智能指针中存在的问题。
1. 存在临时对象构造、析构的开销
2. 由于1,同时存在refCount“跳跃”问题
使用中应该一些原则一定要记清楚,以正确使用该技术:
1. 引用计数类一定要有创建的原始对象
2. RCObject类的拷贝、operator=不会做任何修改refcount的操作,这些操作都是由RCPtr智能指针完成
3. 注意临时对象的存在
下面一章讲解一下,Webkit中对这个技术点的使用,它做了怎么样的方案来解决这个问题的?