当前位置:编程学习 > C/C++ >>

有效的使用和设计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语言 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,