如何将支持范围迭代的COM指针的迭代逻辑抽象为通用模板类
完全可以将迭代逻辑抽象为通用模板,你实现的两个版本核心逻辑高度一致,仅在「枚举器获取逻辑」和「枚举值转目标COM接口逻辑」两处存在差异,把这两部分抽为可定制逻辑即可。
通用模板基础实现
#include <atlbase.h> #include <atlcom.h> // 通用COM集合智能指针模板 // 模板参数说明: // CollectionT: 集合接口类型,如IShellItemArray、IShellWindows // ElementT: 迭代返回的元素接口类型,如IShellItem、IWebBrowser2 // EnumT: 枚举器接口类型,如IEnumShellItems、IEnumVARIANT template <typename CollectionT, typename ElementT, typename EnumT> class CComEnumCollectionPtr : public CComPtr<CollectionT> { public: using CComPtr<CollectionT>::CComPtr; protected: // 子类实现:从集合接口获取枚举器实例 virtual HRESULT GetEnum(CollectionT* pCollection, CComPtr<EnumT>& spEnum) const = 0; // 子类实现:从枚举器读取下一个元素,转换为目标接口类型 virtual HRESULT ParseElement(EnumT* pEnum, CComPtr<ElementT>& spElement) const = 0; private: class CIterator { public: CIterator(const CComEnumCollectionPtr* pOwner) : m_hr(S_FALSE), m_pOwner(pOwner) { CComPtr<EnumT> spEnum; if (m_pOwner && SUCCEEDED(m_pOwner->GetEnum(m_pOwner->p, spEnum))) { m_spEnum = spEnum; ++*this; } } const CIterator& operator++() { m_spElement.Release(); if (m_spEnum) { m_hr = m_pOwner->ParseElement(m_spEnum, m_spElement); } return *this; } BOOL operator!=(HRESULT hr) const { return m_hr != hr; } ElementT* operator*() const { return m_spElement; } private: const CComEnumCollectionPtr* m_pOwner = nullptr; CComPtr<EnumT> m_spEnum; CComPtr<ElementT> m_spElement; HRESULT m_hr = S_FALSE; }; public: CIterator begin() const { return CIterator(this); } HRESULT end() const { return S_FALSE; } };
针对具体集合类型的实现
只需要继承通用模板,实现两个虚方法即可,不需要重复写迭代逻辑。
IShellItemArray 实现
class CShellItemArrayPtr : public CComEnumCollectionPtr<IShellItemArray, IShellItem, IEnumShellItems> { public: using Base = CComEnumCollectionPtr<IShellItemArray, IShellItem, IEnumShellItems>; using Base::CComEnumCollectionPtr; protected: HRESULT GetEnum(IShellItemArray* pCollection, CComPtr<IEnumShellItems>& spEnum) const override { return pCollection->EnumItems(&spEnum); } HRESULT ParseElement(IEnumShellItems* pEnum, CComPtr<IShellItem>& spElement) const override { return pEnum->Next(1, &spElement, nullptr); } };
IShellWindows 实现
class CShellWindowsPtr : public CComEnumCollectionPtr<IShellWindows, IWebBrowser2, IEnumVARIANT> { public: using Base = CComEnumCollectionPtr<IShellWindows, IWebBrowser2, IEnumVARIANT>; using Base::CComEnumCollectionPtr; protected: HRESULT GetEnum(IShellWindows* pCollection, CComPtr<IEnumVARIANT>& spEnum) const override { CComPtr<IUnknown> spUnk; HRESULT hr = pCollection->_NewEnum(&spUnk); if (SUCCEEDED(hr)) { hr = spUnk->QueryInterface(&spEnum); } return hr; } HRESULT ParseElement(IEnumVARIANT* pEnum, CComPtr<IWebBrowser2>& spElement) const override { CComVariant var; HRESULT hr = pEnum->Next(1, &var, nullptr); if (hr == S_OK && var.vt == VT_DISPATCH && var.pdispVal) { hr = var.pdispVal->QueryInterface(&spElement); } return hr; } };
使用方式
和你之前的用法完全一致,直接用范围for遍历即可:
CShellItemArrayPtr spArray; // 初始化spArray的逻辑省略 for (const auto psi : spArray) { // 处理每一个IShellItem对象 }
如果对性能要求极高,不想承担虚函数的微小开销,也可以把GetEnum和ParseElement改为模板策略类参数,编译期绑定逻辑,运行期零开销。
内容的提问来源于stack exchange,提问作者user15284017
相关产品推荐
相关产品推荐

