有效的使用和设计COM智能指针——条款10
条款10:尽量减少智能指针和接口指针的混用
在开始一节之前,让我们先来看一个例子:
view plaincopy to clipboardprint?void func(void)
{
ICalculator *pCalculator = NULL;
CComPtr<ICalculator> pCalculator = NULL;
hrRetCode = CoCreateInstance( //在这里创建COM组件,引用计数为1。
CLSID_CALCULATOR,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICALCULATOR,
(void **)&pCalculator
);
assert(hrRetCode);
spCalculator = pCalculator; //将创建好的组件交给智能指针让其管理引用计数
//赋值运算符会再次调用AddRef()增加引用计数
spCalculator->DoSomething();
} //调用Release()后引用计数不为0,发生了资源泄漏。
void func(void)
{
ICalculator *pCalculator = NULL;
CComPtr<ICalculator> pCalculator = NULL;
hrRetCode = CoCreateInstance( //在这里创建COM组件,引用计数为1。
CLSID_CALCULATOR,
NULL,
CLSCTX_INPROC_SERVER,
IID_ICALCULATOR,
(void **)&pCalculator
);
assert(hrRetCode);
spCalculator = pCalculator; //将创建好的组件交给智能指针让其管理引用计数
//赋值运算符会再次调用AddRef()增加引用计数
spCalculator->DoSomething();
} //调用Release()后引用计数不为0,发生了资源泄漏。
如果不仔细观察,可能很难发现上述例子中存在一个资源泄漏问题。但细致的分析一下,可以发现上述代码中CoCreateInstance中会调用一次接口的AddRef()。而在智能指针赋值过程中又调用了一次AddRef()。但智能指针析构时只有一次Release()调用,因此,这个时候COM的引用技术没有归零,从而不会析构。但函数结束后,没有任何指针能够再次访问到COM组件。因此它在内存中“游离”了,资源泄漏也就产生了。
问题的关键在于接口指针与智能指针混合使用了。而在最后忘记调用接口指针的Release()从而导致了资源泄漏。
可能你会在此函数最末尾补上一句“pCalculator->Release()”,但非常不推荐你这样做。因为我们引入智能指针的目的就是为了尽可能简化我们的开发。若是将智能指针与接口指针混合使用,则会要话费更多的时间来思考引用计数问题。
因此最好的解决办法是将接口指针从函数中完全剔除出来。上述函数完全可以由一下这个例子来完成:
view plaincopy to clipboardprint?void func(void)
{
CComPtr<ICalculator> pCalculator = NULL; //只有智能指针
hrRetCode = pCalculator .CoCreateInstance(CLSID_CALCULATOR,);//引用计数为1
assert(hrRetCode);
spCalculator->DoSomething();
} //调用Release()后引用计数为0,资源正确的被释放了
void func(void)
{
CComPtr<ICalculator> pCalculator = NULL; //只有智能指针
hrRetCode = pCalculator .CoCreateInstance(CLSID_CALCULATOR,);//引用计数为1
assert(hrRetCode);
spCalculator->DoSomething();
} //调用Release()后引用计数为0,资源正确的被释放了
混用智能指针会使得代码引用计数难以琢磨,因此我们不应当在代码中混用智能指针与接口指针。
但凡是都有例外,接口指针并非没有用武之地。而且你已经在之前的章节中见到了这个问题。我们在函数参数传递过程中仍然使用了接口指针。
view plaincopy to clipboardprint?void SomeApp( IHello * pHello )
{
CComPtr<IHello> pCopy = pHello;
OtherApp();
pCopy->Hello();
}
void SomeApp( IHello * pHello )
{
CComPtr<IHello> pCopy = pHello;
OtherApp();
pCopy->Hello();
}
你可能还会有个疑惑,为什么SomeApp( IHello * pHello )这个函数中的参数pHello不使用智能指针呢?
将智能指针放置到函数参数中这种做法并非不行,而是出于以下考虑我们选择了接口指针作为参数传递的类型:
1.如果使用了智能指针,那么此接口将无法在IDL文件中描述。
2.使用智能指针作为参数传递会增加接口的复杂度,造成理解上的困难。
3.使用如果使用第三方的智能指针作为参数的类型,则发布接口的时候需要将此智能指针的实现连同接口一起发布出去。若用户得不到此智能指针的实现,则无法使用此接口。
4.智能指针在传递过程中会产生更大的开销,从而降低程序的性能。
……
若智能指针出现在函数返回值之中,则危害会更大。试想一下如下代码,将会产生何种后果?
view plaincopy to clipboardprint?CComPtr<IView> GetView(int nIndex)
{
CComPtr<IView> spView = NULL;
spView .CreateInstance(CLSID_MYCOMPONENT);
return spView ;
}
CComPtr<IView> GetView(int nIndex)
{
CComPtr<IView> spView = NULL;
spView .CreateInstance(CLSID_MYCOMPONENT);
return spView ;
}
但外面的调用过程如果是这样呢?
view plaincopy to clipboardprint?IView *pIView = NULL;
pIView = GetView();
pIView->DoSomething();
IView *pIView = NULL;
pIView = GetView();
pIView->DoSomething();
我们分析一下过程,GetView()返回了一个临时对象,这个对象是个智能指针,此时他的引用计数为1。当它将此智能指针赋值给另外一个接口指针之后它便析构了。此时引用计数归0,因此COM组件被释放掉。之后随着pIview调用DoSomething()程序崩溃了~
现在你应该可以肯定这一条款:混用智能指针和接口指针会使得引用计数难以琢磨。但函数参数传递和函数返回值中,我们允许接口指针的存在。
作者“liuchang5的专栏”
补充:软件开发 , C语言 ,