最近做的项目,需要在多线程中管理资源,很自然就想到使用智能指针搭配引用计数的技术构造垃圾收集回收机制。首先考虑的是采用loki中的SmartPtr,并且已经在项目中运作,现在才发现Loki::SmartPtr并不是想象中的灵活好用。问题在于SmartPtr的构造函数(用原始指针构造出智能指针对象):
SmartPtr(const StoredType& p) : SP(p)
{
SmartPtrGuard KPGuard(*this, &SmartPtr::OnKPReject);
KP::OnInit(GetImpl(*this));
KPGuard.Dismiss();
}
当你传入一个原始指针给SmartPtr时,会调用该构造函数,这个构造函数并不会执行一次引用计数的递加运算,比如,当你写下这样的代码:
class CA;
void Fun()
{
CA* pCA = new CA; // 引用计数为1
Loki::SmartPtr<CA> spCA1(pA); //引用计数仍然为1
Loki::SmartPtr<CA> spCA2(pA); //引用计数仍然为1
}
会出现什么问题?SmartPtr的缺省ownership策略为RefCounted,Fun返回之前,引用计数一直为1,但当Fun返回的时候,会执行spCA1和spCA2的析构函数,spCA1和spCA2都会递减一次引用计数。递加了1次,却要递减2次,这样使用,问题就出来了。
SmartPtr的这种构造函数不会进行一次AddRef,但是每个析构函数却会毫不犹豫的执行一次Release。
当然,这个问题很容易解决,在每次使用原始指针构造SmartPtr的时候,就调用一次AddRef.比如Fun函数这样写:
void Fun()
{
CA* pCA = new CA; // 引用计数为1
Loki::SmartPtr<CA> spCA1(pA); //引用计数仍然为1
Loki::SmartPtr<CA> spCA2(pA); //引用计数仍然为1
spCA2->AddRef(); //引用计数为2
}
而且,还要注意,spCA1构造完成后,不能调用AddRef,否则就多递加了一次。这种解决方法是很丑陋的,使用智能指针的目的之一就是让它自
己管理资源,当它内部和外部都参与了资源的管理,就显得混乱了。更郁闷的是,我目前发现根据自己定制相关策略也不能解决这类问题。Sma
rtPtr(const StoredType& p)构造函数不会像它的拷贝构造函数一样执行一次OP::Clone操作(内部调用了一次AddRef).最后,其实修改Loki的源代码很容易解决这个问题,在SmartPtr(const StoredType& p)函数的末尾加上一个OP::Clone();并且RefCounted策略构造时把refcount初始化为0;
而boost的intrusive_ptr就是这样做的(boost有好几个智能指针模板,由于我只关心侵入式的引用计数方式,所以只看了这一个的实现:():
intrusive_ptr(T * p, bool add_ref = true): p_(p)
{
if(p_ != 0 && add_ref) intrusive_ptr_add_ref(p_);
}
boost把用原始指针构造智能指针的问题交给用户来决定是否addref一次,并且缺省为执行一次addref。
以强大灵活著称的loki却在这里让我郁闷了。也许这就是设计观念的不同吧