小探ATL的集合和迭代器
ATL的集合和迭代器为什么会存在,我现在也没有像明白,但是可以肯定的是它的存在自有它的理由,
可能我搞这个的日子尚浅还不明白其中的玄机罢了。
相比STL的迭代器,ATL在外面没有什么大的变换,不过里面可是翻江倒海的变化了,
为了那么几个什么next ,reset...简单的接口,它可是花了老劲了
ATL迭代器一般都要支持一个IEnmXXX接口,不过也就是要实现四个方法:Next,Skip,Reset,Clone
就为了这个东西ATL的作者没少花功夫,大量的运用策略模式
CComEnum我们暂且不提了,ATL自己可能都觉得它不能满足现在的用户,它基本上是为支持数组而存在的
但现在喜欢用数组的还有几个,那么多还用的STL数据结构不用事傻子
现在基本都用CComEnumOnSTL,这个东西很是麻烦,6个模板参数,6种策略(号称)
实现了一大堆的东西,说是灵活了其实很复杂。
不过主要是用它来为你自己维护的一个数据集合产生一个迭代器,从而让用户方便的使用你的数据是它的
最终目的。因而你就只需要产生一个这样的迭代器就可以了。
实现的步骤如下:
1、 确定哪个STL容器是需要枚举的
2、 在客户端需要枚举器接口的时候选用适当的模板参数,typedef出一个特定的枚举器对象
3、 CComObject< >::CreateInstance()产生枚举器实例
4、 对产生的枚举器实例调用Init()方法
5、 返回指针和结果信息给客户
假设你需要将你对象中的一个vector<CComVariant>的集合通过这个迭代器暴露给用户,那么
就这么定义。
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
_Copy<VARIANT>, std::vector<CComVariant> > VarVarEnum;
这个类就是这样的,
产生它的实例也容易
就这样:
CComObject<VarVarEnum;>* pEnum = NULL;
HRESULT hr = CComObject<VarVarEnum;>::CreateInstance(&pEnum);
if (FAILED(hr))
return hr;
hr = pEnum->Init(pUnkForRelease, collection);
if (SUCCEEDED(hr))
hr = pEnum->QueryInterface(ppUnk);
if (FAILED(hr))
delete pEnum;
pUnkForRelease 是你包含数据的对象指针,就是你最终给用户的类
collection 是你包含的数据指针, 就是你最终给用户的类一个真正的数据成员
而产生的这个迭代实例就是你最终给用户的类产生的一个对象,这个对象可以访问你的类中的这个数据成员
这样看起来,数据和迭代器是完全不相关的两个东西,看来它这么写和要充分的解耦合有关系
为了很好的创建它,你最好是写个函数
template <class EnumType, class CollType>
HRESULT CreateSTLEnumerator(IUnknown** ppUnk, IUnknown* pUnkForRelease, CollType& collection)
{
if (ppUnk == NULL)
return E_POINTER;
*ppUnk = NULL;
CComObject<EnumType>* pEnum = NULL;
HRESULT hr = CComObject<EnumType>::CreateInstance(&pEnum);
if (FAILED(hr))
return hr;
hr = pEnum->Init(pUnkForRelease, collection);
if (SUCCEEDED(hr))
hr = pEnum->QueryInterface(ppUnk);
if (FAILED(hr))
delete pEnum;
return hr;
}
这个msdn的例子里面的东西,就是用来写这个的
你可以这么用
typedef CComEnumOnSTL<IEnumVARIANT, &IID_IEnumVARIANT, VARIANT,
_Copy<VARIANT>, std::vector<CComVariant> > VarVarEnum;
std::vector<CComVariant> m_vec;
STDMETHOD(get__NewEnum)(IUnknown** ppUnk)
{
return CreateSTLEnumerator<VarVarEnum>(ppUnk, this, m_vec);
}
忘了说了一般你提供给用户的类都是通过(get__NewEnum)来提供一个迭代器的,这个迭代器是用来访问类中的数据的。不过通过上面的分析
你一定要记住,这个返回的接口不是类的接口,而是另外一个对象的接口。
这个东西基本讲清楚了。
下面说说集合,
集合可能就是ATL觉得用户自己写这个集合不太放心,因为本来就比较麻烦,不如就帮我们写了
ATL中有个叫做ICollectionOnSTLIml的类帮我们搞定了这一切麻烦的事情。
看看吧
STDMETHOD(get_Count)(long* pcount)
{
if (pcount == NULL)
return E_POINTER;
ATLASSUME(m_coll.size()<=LONG_MAX);
*pcount = (long)m_coll.size();
return S_OK;
}
STDMETHOD(get_Item)(long Index, ItemType* pvar)
{
//Index is 1-based
if (pvar == NULL)
return E_POINTER;
if (Index < 1)
return E_INVALIDARG;
HRESULT hr = E_FAIL;
Index--;
CollType::const_iterator iter = m_coll.begin();
while (iter != m_coll.end() && Index > 0)
{
iter++;
Index--;
}
if (iter != m_coll.end())
hr = CopyItem::copy(pvar, &*iter);
return hr;
}
STDMETHOD(get__NewEnum)(IUnknown** ppUnk)
{
if (ppUnk == NULL)
return E_POINTER;
*ppUnk = NULL;
HRESULT hRes = S_OK;
CComObject<EnumType>* p;
hRes = CComObject<EnumType>::CreateInstance(&p);
if (SUCCEEDED(hRes))
{
hRes = p->Init(this, m_coll);
if (hRes == S_OK)
hRes = p->QueryInterface(__uuidof(IUnknown), (void**)ppUnk);
}
if (hRes != S_OK)
delete p;
return hRes;
}
CollType m_coll;
我们要实现的类的数据部分基本就要这么写的,继承它就写了,岂不是很轻松,不过它真正的数据成员是m_coll,上面
写的并没有对它进行初始化,所以在我们实现的类的初始化中一定要初始化这个类。
至于怎么用它,ATL的书里面应该是都有介绍吧,基本就是写好那些复杂的模板参数参数,同时如果希望生成和数据不同类型的
迭代器也是有办法的,你只需要写个带转换的copy函数就可以了,这个都可以通过模板参数来变化的。这个就是将数据和迭代器
分离的好处。
至于CAdapt就是简单的封装,目的是为了对付&操作符直接将ATL智能类型直接将内部数据指针直接暴露的问题,一般就是这样子,
没有什么玄机,不信自己看就知道了,很简单的
template <class T>
class CAdapt
{
public:
CAdapt()
{
}
CAdapt(__in const T& rSrc) :
m_T( rSrc )
{
}
CAdapt(__in const CAdapt& rSrCA) :
m_T( rSrCA.m_T )
{
}
CAdapt& operator=(__in const T& rSrc)
{
m_T = rSrc;
return *this;
}
bool operator<(__in const T& rSrc) const
{
return m_T < rSrc;
}
bool operator==(__in const T& rSrc) const
{
return m_T == rSrc;
}
operator T&()
{
return m_T;
}
operator const T&() const
{
return m_T;
}
T m_T;
};
就是没有&操作符而已,希望能够直接将智能类型指针传入集合中而产生的一个东西。
同时不要小看了ATL 它同样也有CAtlArray CAtlList CAtlMap,不一定非要用STL.