條款2:引用計數的是與非
使用COM做開發的程序員往往會被接口引用計數所帶來的問題搞得頭破血流。引用計數這個老大難問題存在的原因也相當簡單:在COM開發中,客戶僅僅知道組件的接口。當使用完一個接口而要使用另外一個接口時,由於客戶並不知道兩個接口是否指向同一組件,因此客戶無法直接將組件釋放。COM組件的生命周期也因此無法被客戶方便的控制。
於是為了解決這一問題,COM組建使用了引用計數。每個組件根據引用計數的數值決定何時釋放自身所占用的資源。當客戶從某個組件查詢出一個接口時,此引用計數值將增1。當客戶使用完此接口時,此引用值減1。如果某個組件的引用計數值降至0時候,組件則會將自己從內存中刪除。
通過引用計數確實可以很合理的管理組件的生命周期,但也嚴格要求開發人員遵循下面這三條簡單規則【1】:
1.在返回之前調用AddRef。對於那些返回接口指針的函數,在返回前應用相應的指針調用AddRef。這些函數包括QueryInterface及CreateInstance。這樣當客戶從這種函數得到一個接口後,他將無需調用AddRef。
2.使用完接口之後調用Release。在使用完某個接口之後應調用此接口的Release函數。
3.在賦值之後調用AddRef。將一個接口指針賦值給另外一個接口指針時,應調用AddRef。換句話說,在建立接口的另外一個引用之後應增加相應組件的引用計數。
根據如上三條原則我們需要編寫出了形如下例的代碼:
view plaincopy to clipboardprint?void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef();
OtherApp();
pCopy->Hello();
pCopy->Release();
}
void SomeApp( IHello * pHello )
{
IHello* pCopy = pHello;
pCopy->AddRef();
OtherApp();
pCopy->Hello();
pCopy->Release();
}
這三條規則看似簡單,但是程序員若在某一時刻遺漏掉其中的某條規則,則會使得引用計數陷入混亂。出錯後,最樂觀的情況是程序由於訪問了已經被釋放的資源,而直接崩潰。如果運氣不是特別好,那麼資源洩露則悄無聲息的發生了。
可以看出由於引用計數的引入,COM組件的生命周期可以自行管理,但同時也使得COM的使用變得非常危險。因為使用過程中需要每一個使用者都要嚴格並且正確的調用AddRef()和Release(),一旦出現問題,就會造成對象不能被正常釋放,或者對象被重復刪除,造成程序崩潰。所以使用COM接口,必須小心翼翼才行。
我們試著用智能指針改寫上述代碼。這裡以CComPtr為例,更多關於它的介紹我們放到後續條款中。上述函數將會變成如下這種形式:
view plaincopy to clipboardprint?void SomeApp( IHello * pHello )
{
CComPtr<IHello> spHello= pHello;
OtherApp();
spHello->Hello();
}
void SomeApp( IHello * pHello )
{
CComPtr<IHello> spHello= pHello;
OtherApp();
spHello->Hello();
}
智能指針給我帶來了引用計數的半自動化處理(之所一說是半自動化,是因為有些地方仍然需要手動管理引用計數)。AddRef()和Release()操作不見了,代碼簡潔了不少,而更重要的是它還將帶來更多的便利條件。我們在之後的條款中會進一步討論。
作者“liuchang5的專欄”