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

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

編輯:關於C語言

條款13:必須提前釋放COM組件時,別妄想智能指針幫你完成
有了智能指針,或許你不會想到要自己手動釋放或者增加引用計數了。那麼請欣賞一下下面這個函數:

view plaincopy to clipboardprint?void InSomewhere(IHello *pHello) 

    CComPtr<IHello> spHello = pHello; 
    spHello->DoSomething(); 
    HRESULT hr =CoCreateInstance( 
            CLSID_HELLO 
            NULL, 
            CLSCTX_INPROC_SERVER, 
            IID_IHELLO, 
            (void **)&spHello     //這裡可能出現資源洩漏  
    ); 
    assert(SUCCEED(hr)); 
    spHello->DoOtherthing(); 

void InSomewhere(IHello *pHello)
{
    CComPtr<IHello> spHello = pHello;
    spHello->DoSomething();
    HRESULT hr =CoCreateInstance(
            CLSID_HELLO
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_IHELLO,
            (void **)&spHello     //這裡可能出現資源洩漏
    );
    assert(SUCCEED(hr));
    spHello->DoOtherthing();
}
你認真的核對了一下IID和指針的類型,發現沒有問題。並且CoCreateInstance後面這個assert讓你對他的行為信心十足。

但是問題是spHello原來是有所指的。他會釋放掉相應接口的引用計數嗎?答案是未定義。

對於spHello在非空情況下的取地址操作,在不同智能指針中定義不盡相同。對CComPtr來說,他會在DEBUG的時候出發一個斷言,而在RELEASE版本就悄悄的讓資源洩漏了。而對於_com_ptr_t而言,無論是在RELASE還是DEBUG版本中它都會偷偷的先釋放掉原油資源,而不給外界任何關於這種危險操作的提示。

知道了原因可能你不會太期望用取地址符這種危險的操作了。你可能想到了用前面“條款11:以類型安全的方式創資源和查詢接口”所講述的內容。看看下面這個修改版會不會存在問題。

view plaincopy to clipboardprint?void InSomewhere(IHello *pHello) 

    CComPtr<IHello> spHello = pHello; 
    spHello->DoSomething(); 
    spHello.CoCreateInstance( CLSID_HELLO );//這裡可能出現資源洩漏  
    assert(spHello); 
    spHello->DoOtherthing(); 

void InSomewhere(IHello *pHello)
{
    CComPtr<IHello> spHello = pHello;
    spHello->DoSomething();
    spHello.CoCreateInstance( CLSID_HELLO );//這裡可能出現資源洩漏
    assert(spHello);
    spHello->DoOtherthing();
}
問題還是發生了,因為CoCreateInstance仍然只是在DEBUG版本中spHello非空的情況下做了一個斷言。而RELEASE中資源就洩漏了。而對於_com_ptr_t來說,仍然是悄悄的先幫你把資源給釋放掉。

因此最穩妥的應對策略是,“必須提前釋放COM組件時,別妄想智能指針幫你完成”。

view plaincopy to clipboardprint?void InSomewhere(IHello *pHello) 

    CComPtr<IHello> spHello = pHello; 
    spHello->DoSomething(); 
    spHello = NULL;             //或者spHello.Release(); 提前釋放資源  
    spHello.CoCreateInstance( CLSID_HELLO ); 
    assert(spHello); 
    spHello->DoOtherthing(); 

void InSomewhere(IHello *pHello)
{
    CComPtr<IHello> spHello = pHello;
    spHello->DoSomething();
    spHello = NULL;             //或者spHello.Release(); 提前釋放資源
    spHello.CoCreateInstance( CLSID_HELLO );
    assert(spHello);
    spHello->DoOtherthing();
}
OK,問題解決了。

更有一些情況下我們不知覺的導致了內存洩漏,原因同樣是由於你沒有手動釋放COM組件。假設我們設計了這麼一個容器來存放智能指針如下:

view plaincopy to clipboardprint?template<typename T> 
class MyStack 

public: 
    MyStack(int nCapacity){ 
        m_pArray = new T[nCapacity](); 
        m_nCapacity = nCapacity; 
        m_nTop= 0; 
    }; 
    ~MyStack(){ 
        delete[] m_pArray; 
    }; 
    void push(T Item){ 
        assert(m_nTop < m_nCapacity); 
        m_pArray[m_nTop++] = Item; 
    } 
    T pop(){ 
        assert(m_nTop > 0); 
        return m_pArray[--m_nTop]; 
    } 
    MyStack(const MyStack<T>& otherArray) 
    { 
        //deep copy it   
    }; 
private: 
    T *m_pArray; 
    int m_nTop; 
    int m_nCapacity; 
}; 
template<typename T>
class MyStack
{
public:
    MyStack(int nCapacity){
        m_pArray = new T[nCapacity]();
        m_nCapacity = nCapacity;
        m_nTop= 0;
    };
    ~MyStack(){
        delete[] m_pArray;
    };
    void push(T Item){
        assert(m_nTop < m_nCapacity);
        m_pArray[m_nTop++] = Item;
    }
    T pop(){
        assert(m_nTop > 0);
        return m_pArray[--m_nTop];
    }
    MyStack(const MyStack<T>& otherArray)
    {
        //deep copy it
    };
private:
    T *m_pArray;
    int m_nTop;
    int m_nCapacity;
};

 

而使用這個MyStack的過程是如下這樣的:

view plaincopy to clipboardprint?MyStack<CComPtr<ICalculator>> g_myStack(1000); 
void func() 

    for (int i=0; i<500; i++) 
    { 
        CComPtr<ICalculator> spCalculator = NULL; 
        spCalculator.CoCreateInstance(CLSID_CALCULATOR);  
        g_myStack.push(spCalculator); 
    } 
        
    ... 
     
    for (int i=0; i<500; i++) 
    { 
        CComPtr<ICalculator> spCalculator = g_myStack.pop(); 
        spCalculator->DoSomething(); 
    } 

MyStack<CComPtr<ICalculator>> g_myStack(1000);
void func()
{
    for (int i=0; i<500; i++)
    {
        CComPtr<ICalculator> spCalculator = NULL;
        spCalculator.CoCreateInstance(CLSID_CALCULATOR);
        g_myStack.push(spCalculator);
    }
      
    ...
   
    for (int i=0; i<500; i++)
    {
        CComPtr<ICalculator> spCalculator = g_myStack.pop();
        spCalculator->DoSomething();
    }
}
500個CoCreateInstance()這個操作或許會讓你覺得有點瘋狂。但我僅僅是想讓你知道內存洩漏是如何潛在的發生的。

其原因是什麼? 智能指針無法在這種情況下自動幫我們釋放掉相應的資源,首先他在全局空間上。如果想等待~MyStack()這個析構函數被調用可能需要等到程序結束。而在pop後我們不應當繼續在MyStack中保存智能指針對接口的引用。試想,若之後這個MyStack中的元素永遠達不到500個。那麼,MyStack後半部分所持有的資源永遠無法被釋放掉。

因此必須手動釋放COM組件時,別妄想智能指針幫你完成,稍微改寫一下pop()函數,資源洩漏問題解決了。

view plaincopy to clipboardprint?template<typename T> 
class MyStack 

public: 
    .... 
    T pop(){ 
        assert(m_nTop > 0); 
        T tempArray = m_pArray[--m_nTop]; 
        m_pArray  = NULL;        //或者用 m_pArray.Release();  
        return  tempArray; 
    } 
private: 
    .... 
}; 
template<typename T>
class MyStack
{
public:
    ....
    T pop(){
        assert(m_nTop > 0);
        T tempArray = m_pArray[--m_nTop];
        m_pArray  = NULL;        //或者用 m_pArray.Release();
        return  tempArray;
    }
private:
    ....
};


謹記在心,智能指針只是輔助我們管理內存的手段。必須提前釋放COM資源時,別妄想智能指針幫你完成。這一節可以結束了。

 作者“liuchang5的專欄”

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