引用计数我不怕之智能指针

前言

使用引记数,就算是再历害的高手也难免会出错。而一但出错了,之后再去查问题可就相当的困难了。正如我曾经看到,有一段代码是这样的:

m_spView->Release(); 
m_spView->Release(); 
m_spView->Release();

看到这段代码,就知道引用计数出问题了。他想通过这种方式,把多出来的计数Release掉。但这么做能解决问题吗?答案是不能,这样的代码还可能造成严重的稳定性问题。解决引用计数问题,除了要了解引用计数规则外,我们还提昌要用智能指针。智能能帮助我们很好的处理引用计数问题。

智能指针的差异

在用VC开发应用程序时,有两个引用计数类可供我们使用。_com_ptr_t与CComPtr,它们都能很好的帮助我们解决引用计数处理。但这两个类还是有一点小小的区别,有的时候这一点区别也是致命的,因此我们必须清楚它们的差别。下面我罗列了它们之间的差别:

1. CComPtr的&运算符不会释放原指针,而_com_ptr_t会释放原指针。

2. CComPtr对AddRef与Release做了限制,也就是不充许调用这两个方法,而_com_ptr_t并没有限制。

3. CComPtr只能接受模版参数指定的指针,_com_ptr_t可以接受任何接口指针,并自动调用QueryInterface得到模板参数指定的指针类型。

这些区别,导致了有些代码不能同时应用于两个智能指针。

&运算符差异带来的风险

HRESULT hr GetView(int i, /*out*/IView** ppView) 

*ppView = m_Views[i]; 
(*ppView)->AddRef();

return S_OK; 

CComPtr<IView> spView; 
for (int i = 0; i < 10; i++) 

GetView(i, &spView); 
spView->… 
}

以上代码会导致引用计数出错,前面的9个View的引用计数并没有Release。CComPtr<IView>的&运算符,会返回IView**也就是CComPtr内部成员的地址,但它不释放原来的指针。而GetView又会修改指针,直接把原来的指针抛弃了。

这个代码可以这样改:

for (int i = 0; i < 10; i++) 

CComPtr<IView> spView; 
GetView(i, &spView); 
spView->… 
}

把指针作为循环的局部变量,这样每次循环退出前spView都会被析构,最终调用Release。当然还能这样改:

COM_SMARTPTR_TYPEDEF(IView, __uuidof(IView)); 
IView Ptr spView; 
for (int i = 0; i < 10; i++) 

GetView(i, &spView); 
spView->… 
}

_com_ptr_t的&运算符会帮助我们把原来的指针Release掉,所以我们就不必担心引用计数没有释放。

禁用AddRef与Release

然我们使用的智能指针,就不要再去调AddRef或Release了。如果再去手工调用它们,就失去了智能指针的好处。CComPtr有一个非常巧妙的方法,禁止调用这两个方法。它声明了一个类_NoAddRefReleaseOnCComPtr,它的定义如下:

template <class T> 
class _NoAddRefReleaseOnCComPtr : public T 

private: 
STDMETHOD_(ULONG, AddRef)()=0; 
STDMETHOD_(ULONG, Release)()=0; 
};

我们看到,里面就定义了两个私有函数。AddRef与Release,它们重写了IUnknown的这两个方法,并且继承自模板T。再来看段代码:

_NoAddRefReleaseOnCComPtr<T>* operator->() const throw() 

ATLASSERT(p!=NULL); 
return (_NoAddRefReleaseOnCComPtr<T>*)p; 
}

我们看到的是CComPtr的“->”运算符,它将内部的指针强制转换成_NoAddRefReleaseOnCComPtr<T>*。其中T是CComPtr的模板参数,也就是接口指针类型。可以看出_NoAddRefReleaseOnCComPtr<T>继承自接口类型,因此通过_NoAddRefReleaseOnCComPtr<T>*可以调用T的所有函数。前面我们看到NoAddRefReleaseOnCComPtr的两个私用函数,AddRef与Release,如果有谁想调用就会报编译错误。

自动QueryInterface

_com_ptr_t有多个=运算符版本,代码如下:

template<typename _IIID> class _com_ptr_t 

public: 
typedef _IIID ThisIIID; 
typedef typename _IIID::Interface Interface; 
// Queries for interface. 
template<typename _InterfaceType> _com_ptr_t& operator=(_InterfaceType* p) 

HRESULT hr = _QueryInterface(p); 
if (FAILED(hr) && (hr != E_NOINTERFACE)) { 
_com_issue_error(hr); 

return *this; 

// Saves the interface. 
template<> _com_ptr_t& operator=(Interface* pInterface) throw() 

if (m_pInterface != pInterface) { 
Interface* pOldInterface = m_pInterface; 
m_pInterface = pInterface; 
_AddRef(); 
if (pOldInterface != NULL) { 
pOldInterface->Release(); 


return *this; 
}

其中 
template<typename _InterfaceType> _com_ptr_t& operator=(_InterfaceType* p)是一个模板函数,接受任意类型的指针,函数内部会调用传入参数“p”的QueryInterface。

template<> _com_ptr_t& operator=(Interface* pInterface) throw()是模板函数的一个偏特化版本,接受_com_ptr_t模板参数中指定的指针类型。当传入的接口指针类型,与类模板指定的类型一样时这个函数会被调用。它不需要做QueryInterface的调用,只是简单的AddRef;

综上所述,两个智能指针在同一份代码里混用,很可能导致不良后果。所以我认为,最好不要在同一份代码里混用。而这两个指针,我很喜欢_com_ptr_t,它在许多方面明显优于CComPtr。

Attach与Detach

使用了智能指针,也并不是高枕无忧了。它还是给我们带来了一些新的问题。

有一些第三方类库设计的不合理,它在函数的返回值里返回接口指针。如下代码就导会导致引用计数泄漏:

IView* GetView(int nIndex)

{

IView* pView = m_Views[nIndex];

pView->AddRef();

return pView;

}

IViewPtr spView = GetView(0);

以上代码,注意调用GetView的地方。IViewPtr是智能指针,它的=运算符是会再调用AddRef而GetView里已经调了一次AddRef了,这里多了一次AddRef。别问我GetView中为什么要AddRef,这是引用计数规则,不清楚请看《引用计数我不怕之引用计数规则》

解决这个问题的方法就是用Attach函数

IViewPtr spView;

spView. Attach(GetView(0));

也许是有人写了Attach,而其它人不明白Attach的意思,结果写出了这样的代码。

void SetView(IView* pView)

{

m_spView.Attach(pView);

}

根据引用计数规则,将指针保存为副本,必须AddRef。但是这个例子里没有这么干,结果m_spView变成了野指针。

前面我们看到的GetView很简单,但是下面我们要做一别的事情,于是要用智能指针。

HRESULT hr GetView(int nIndex, IView** ppView)

{

IViewPtr spView = m_Views[nIndex];

if (spView->IsVisable() != S_OK)

return E_FAILD;

*ppView = spView;

return S_OK;

}

表面看来没什么问题,但在函数返回后,智能指针又会调用一次Release。要解决这个问题,可以调用Detach。

HRESULT hr GetView(int nIndex, IView** ppView)

{

IViewPtr spView = m_Views[nIndex];

if (spView->IsVisable() != S_OK)

return E_FAILD;

*ppView = spView. Detach();

return S_OK;

}

Detach还是会被乱用,看到这样的代码还真是哭笑不得。

HRESULT hr ChangeView(int nIndex)

{

IViewPtr spView = m_Views[nIndex].Detach();

spView->Change();

return S_OK;

}

这段代码能导致两个问题

1. 引用计数泄漏

2. m_Views中的指针变成空了

泄漏是由于Detach返回IView*,并不会Release,而spView又会再调用一次AddRef。智能指针的Detach是会把自己设成空的,否则还叫什么Detach。

使用智能指针,是解决引用计数问题最好的办法。不要因为用智能指针,会引入新的问题,而放弃使用它。只要花心思搞清楚智能指针的不同点,使用时注意一些细节问题,使用起来应该会变的非常轻松。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值