主要思路,对来值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中对这个技术点的使用,它做了怎么样的方案来解决这个问题的?