傳統手動管理引用計數代碼:
void SomeApp(IHello* pHello) {
IHello* pCopy = pHello;
pCopy->AddRef();
OtherApp();
pCopy->Hello();
pCopy->Release();
}
看似無問題,但若 OtherApp() 拋出異常,Release() 永不執行 → COM 組件無法釋放 → 資源洩漏。消除異常極難:第三方庫可能拋異常、C++ 預設操作(流、動態記憶體、dynamic_cast)可能拋異常。與其消滅異常副作用,不如用智能指標從根解決:
void SomeApp(IHello* pHello) {
CComPtr spHello = pHello;
OtherApp(); // 若拋異常,spHello 出作用域析構 → 自動 Release()
spHello->Hello();
}
優勢:AddRef/Release 消失、邏輯清晰、異常安全(棧展開自動析構釋放)。
手動版:
IUnknown* pIUnknown = CreateInstance();
IX* pIX = NULL;
hr = pIUnknown->QueryInterface(IID_IX, (void**)&pIX);
if (SUCCEEDED(hr)) {
pIX->Fx(); // 核心邏輯
IX* pIX2 = pIX; // 但引用計數操作佔一半代碼
pIX2->AddRef();
pIX2->Fx();
pIX2->Release();
pIX->Release();
}
因 pIX2 生命週期與 pIX 相同,實無需 AddRef/Release。智能指標版:
CComPtr spIX = NULL;
hr = spIX.CoCreateInstance(CLSID_MYCOMPONENT);
if (SUCCEEDED(hr)) {
spIX->Fx(); // 核心邏輯
CComPtr spIX2 = spIX; // 僅剩邏輯 :)
spIX2->Fx();
}
手動版需層層 if (SUCCEEDED) 巢狀,Release 與 GetObject 成對,易漏、難讀、需拖捲軸:
void f() {
IUnknown* rgpUnk[3];
hr = GetObject(&rgpUnk[0]);
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[1]);
if (SUCCEEDED(hr)) {
hr = GetObject(&rgpUnk[2]);
if (SUCCEEDED(hr)) {
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
rgpUnk[2]->Release();
}
rgpUnk[1]->Release();
}
rgpUnk[0]->Release();
}
}
智能指標版:優雅扁平化:
void f() {
CComPtr rgpUnk[3];
if (FAILED(GetObject(&rgpUnk[0]))) return;
if (FAILED(GetObject(&rgpUnk[1]))) return;
if (FAILED(GetObject(&rgpUnk[2]))) return;
UseObjects(rgpUnk[0], rgpUnk[1], rgpUnk[2]);
}
關鍵邏輯 UseObjects 一目瞭然。
手動 CoCreateInstance 易犯錯:IID_IY 配 IX*,void** 參數型別不安全。智能指標綁定 IID 與型別:
CComPtr spIX;
hr = spIX.CoCreateInstance(CLSID_MYCOMPONENT); // 無 IID、無 void**
spIX->fun();
_com_ptr_t 更進一步:建構函數直接建立組件:
_COM_SMARTPTR_TYPEDEF(ICalculator, __uuidof(ICalculator));
ICalculatorPtr spIX(CLSID_MYCOMPONENT);
spIX->fun();
除建立、斷言、調用外,零冗餘代碼。注意:智能指標提供「類型安全操作」,非「絕對類型安全」(詳見條款 13)。
—— 作者:liuchang5 的專欄