使用 COM 開發的程式設計師,常被介面引用計數問題搞得頭破血流。問題根源簡單:客戶端僅知組件介面,不知兩個介面是否指向同一組件,故無法直接釋放組件,導致組件生命週期無法由客戶端便利控制。
為解此困境,COM 採用引用計數:組件依計數值決定何時釋放資源。客戶端查詢介面時計數 +1,用完呼叫 Release() 計數 -1,計數歸零時組件自行從記憶體刪除。
引用計數雖合理,但嚴格要求開發者遵循三條規則:
AddRef():返回介面指標的函數(QueryInterface、CreateInstance 等)需在返回前呼叫 AddRef(),使客戶端無需再呼叫。Release():用完介面後必須呼叫其 Release()。AddRef():將一個介面指標賦值給另一個時,需呼叫 AddRef(),即建立新引用時增加計數。依此編寫的程式碼如下:
void SomeApp(IHello* pHello) {
IHello* pCopy = pHello;
pCopy->AddRef(); // 規則 3
OtherApp();
pCopy->Hello();
pCopy->Release(); // 規則 2
}
三條規則看似簡單,但程式設計師若在某時刻遺漏任一規則,引用計數即陷入混亂。樂觀情況下程式因存取已釋放資源而崩潰;運氣不好時,資源洩漏悄無聲息發生。引用計數雖讓組件生命週期自管,卻使 COM 使用變得極度危險——每個使用者都必須嚴格正確呼叫 AddRef()/Release(),一旦出錯,輕則物件無法釋放,重則重複刪除導致崩潰。
以 CComPtr 為例改寫上述程式碼:
void SomeApp(IHello* pHello) {
CComPtr<IHello> spHello = pHello;
OtherApp();
spHello->Hello();
}
AddRef() 與 Release() 操作消失,程式碼大幅精簡,更重要的是帶來更多便利(後續條款詳細討論)。之所以說是「半自動化」,因為部分場合仍需手動管理引用計數。
—— 作者:liuchang5 的專欄