程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> 有效的使用和設計COM智能指針——條款10

有效的使用和設計COM智能指針——條款10

編輯:關於C語言

條款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的專欄”

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved