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