如何使用 Boost iterator_facade 将 MFC 集合包装成符合 STL 标准的迭代器
如何使用 Boost iterator_facade 将 MFC 集合包装成符合 STL 的迭代器。
引言
本文需要来自 Boost 网站 的 Boost iterator 头文件。这里描述的方法可以应用于任何集合,不仅仅是 MFC。但是,为了本文的目的,我将展示如何为 MFC 集合类实现符合 STL 的迭代器。
背景
迭代器提供统一的访问方法来访问在实现细节上不相关的集合。这不仅适用于“实现细节”,也适用于整个概念。例如,可以为目录和文件结构创建迭代器(查看 Boost 文件系统库,位于 此处)。你可以为任何代表“多”的事物编写迭代器。例如,监视器、串行端口、TCP 连接、XML 节点、HTTP 标签等等。此外,迭代器使得底层集合能够被标准模板库(STL)提供的算法(如 for_each
、search
、find
、find_if
、find_end
、find_first_of
、count
、count_if
、equal
、search_n
、sort
等)或需要标准迭代器的第三方库所操作。
MFC 集合主要分为三大类:索引数组(CArray
、CPtrArray
、CObArray
、CTypedPtrArray
、CStringArray
、CByteArray
、CDWordArray
、CWordArray
、CUIntArray
)、链表(CList
、CPtrList
、CObList
、CTypedPtrList
、CStringList
)和映射(CMap
、CMapPtrToWord
、CMapPtrToPtr
、CMapStringToOb
、CMapStringToPtr
、CMapStringToString
、CMapWordToOb
、CMapWordToPtr
)。每种类别的遍历/访问方式都不同,因此需要根据所使用的集合进行特定的实现。请看以下示例:
CArray
迭代
//*************************** // CArray //*************************** CArray < int > array_of_ints; // Add 10 integers to CArray for(int i=0; i<10; i++) array_of_ints.Add(i); // Print out contents of CArray for(int i=0; i < array_of_ints.GetSize(); i++) cout << array_of_ints.GetAt(i);
CList
迭代
//************************** // CList //************************** CList < int > list_of_ints; // Add 10 integers to CList for(int i=0; i<10; i++) list_of_ints.AddTail(i); // Print out contents of CList for(POSITION pos = list_of_ints.GetHeadPosition(); pos != NULL; ) cout << list_of_ints.GetNext(pos) << endl;
CMapStringToString
迭代
//*********************** // CMapStringToString //*********************** cout << "CMapStringToString. Note that printout" " may not be in sequence" << endl; CMapStringToString mapStr; for(int i=0; i<10; i++) { CString s; s.Format("%d", i); mapStr[s] = s; } // Print out CMapStringToString CString sVal; CString sKey; for(POSITION pos = mapStr.GetStartPosition(); pos != NULL;) { mapStr.GetNextAssoc(pos, sKey, sVal); cout << sVal << endl; }
如你所见,每种集合类别的代码都截然不同;即便如此,每个代码片段都试图完成同一件事——将集合的内容打印到 cout
。
此外,MFC 库中的某些核心类本身就是容器。CWinApp
类是 CDocTemplate
对象的容器,CDocTemplate
类是 CDocument
对象的容器,而 CDocument
类是 CView
对象的容器。以下代码展示了如何访问 CDocTemplate
对象:
// Iterate through CDocTemplates POSITION pos = AfxGetApp()->GetFirstDocTemplatePosition(); while(pos) CDocTemplate* pTempl = AfxGetApp()->GetNextDocTemplate(pos); // Iterate through CDocuments POSITION pos = pTempl->GetFirstDocPosition(); while(pos) CDocument* pDoc = pTempl->GetNextDoc(pos); // Iterate throuh CDocument views POSITION pos = pDoc->GetFirstViewPosition(); while(pos) CView* pView = pDoc->GetNextView(m_pos);
有可能以统一的方式概括访问所有 MFC 集合。这个过程在“泛型编程”中称为 **“提升” (Lifting)**。有关更多信息,请参阅此 链接。
提升 MFC 集合
与其从头开始实现迭代器,不如使用 boost::iterator_facade
类作为基类来提升 MFC 集合。在这种情况下,我们只需要编写最少量的代码,但最终仍然会得到一个工业级、符合 STL 的迭代器。
为了实现这个目标,我们需要一个代码骨架,它将提供最基本的函数:
begin();
end();
increment();
decrement();
请注意,如果集合是仅向前移动的,如CWinApp::GetNextDocTemplate()
(因为没有GetPrevDocTemplate()
函数),则不需要decrement
。equal();
dereference();
可能还需要实现其他函数。例如,像这样的代码:
my_iterator it = my_iterator.begin() + 10;
将需要实现 advance(difference_type n);
函数。好消息是编译器会告诉你该函数缺少,你只需要提供一个即可。
对于所有 MFC 集合,我们的 MFC 骨架代码将如下所示:
// // MFC Iterator skeleton // template < typename _Type > class mfc_iterator : public boost::iterator_facade < mfc_iterator, _Type, boost::forward_traversal_tag /* boost::bidirectional_traversal_tag */ > { public: /////////////////////////////////////////////// /// TODO: Write begin code logic here /////////////////////////////////////////////// mfc_iterator & begin() { //.... return *this; } /////////////////////////////////////////////// /// just call default private contructor. /// end is a state when everything in this iterator is set to NULL /// or whatever variables values you choose for that /////////////////////////////////////////////// mfc_iterator end() { return mfc_iterator (); } private: friend class boost::iterator_core_access; /////////////////////////////////////////////// /// TODO: Write end() constructor here. See end() /////////////////////////////////////////////// mfc_iterator ():m_node(0) {} /////////////////////////////////////////////// /// TODO: Write increment code here /////////////////////////////////////////////// void increment() { //.... } /////////////////////////////////////////////// /// TODO: Write decrement code here /////////////////////////////////////////////// void decrement() { //.... } /////////////////////////////////////////////// /// TODO white compare criteria here /////////////////////////////////////////////// bool equal(view_iterator const & other) const { //.... } /////////////////////////////////////////////// /// /////////////////////////////////////////////// _Type& dereference() const { return *m_node; } _Type* m_node; };
信不信由你,我们差不多完成了。开玩笑的。现在我们只需要为相应的 MFC 集合类型填补空白。其余的优点都由 boost::iterator 库提供。它包括后置/前置++、后置/前置--,以及许多其他标准的迭代器项。
让我们为 CArray 类填补空白。
提升的 CArray
// // CArray iterator // #include < boost/iterator/iterator_facade.hpp > template < typename _Type, typename _Tcontainer = CArray<_Type>> class mfc_carray_iterator : public boost::iterator_facade < mfc_carray_iterator < _Type, _Tcontainer >, _Type, boost::bidirectional_traversal_tag > { _Type m_node; // element type _Tcontainer* m_pCont; // Pointer to underlying CArray container INT_PTR m_nIndex; // current CArray index public: /////////////////////////////////////////////// /// /////////////////////////////////////////////// explicit mfc_carray_iterator(_Tcontainer* pCont) : m_node(0) , m_nIndex(0) , m_pCont(pCont) {} /////////////////////////////////////////////// /// Point to first element in CArray /////////////////////////////////////////////// mfc_carray_iterator& begin() { m_nIndex = 0; if(m_pCont->GetSize() == 0) // safety checks *this = end(); else m_node = m_pCont->GetAt(m_nIndex); return *this; } /////////////////////////////////////////////// /// just call private conrtuctor /////////////////////////////////////////////// mfc_carray_iterator end() { return mfc_carray_iterator(); } private: friend class boost::iterator_core_access; /////////////////////////////////////////////// /// constructs end() /////////////////////////////////////////////// mfc_carray_iterator(): m_node(0) , m_nIndex(0) , m_pCont(0) { } /////////////////////////////////////////////// /// Increment CArray /////////////////////////////////////////////// void increment() { // CArray specific increment code m_nIndex++; if(m_nIndex >= m_pCont->GetSize())// safety checks { *this = end(); return; } m_node = m_pCont->GetAt(m_nIndex); } /////////////////////////////////////////////// /// Decrement CArray /////////////////////////////////////////////// void decrement() { // CArray specific decrement code m_nIndex--; if(m_nIndex >= m_pCont->GetSize()) // safety checks return; m_node = m_pCont->GetAt(m_nIndex); } /////////////////////////////////////////////// /// /////////////////////////////////////////////// bool equal(mfc_carray_iterator const& other) const { return m_pCont == other.m_pCont && m_node == other.m_node && m_nIndex == other.m_nIndex; } _Type& dereference() const { return (_Type&) m_node; } };
我们现在可以使用以下代码片段来测试我们的 CArray
迭代器:
#include < boost/iterator/iterator_facade.hpp > #include < algorithm > // Use functor to populate out CArray struct F_Generate { int m_nValue; CArray < int > * m_pArray; F_Generate(CArray < int > * pArray): m_nValue(0), m_pArray(pArray){} int operator()() { m_pArray->GetAt(m_nValue) = m_nValue++; return m_nValue; } }; void print_int(int i) { cout << i << endl; } int main(int argc, TCHAR* argv[], TCHAR* envp[]) { CArray < int > array_of_ints; // Add 10 integers to CArray //for(int i=0; i<10; i++) // array_of_ints.Add(i); // We going to generalize array population array_of_ints.SetSize(10); mfc_carray_iterator < int > it(&array_of_ints); // generate does the same job as commented out for loop. // Note how structure of the program becomes generic //ripe for further lifting std::generate(it.begin(), it.end(), F_Generate(&array_of_ints)); for_each(it.begin(), it.end(), print_int); return 0; }
提升的 CList
// // CList iterator // #include < boost/iterator/iterator_facade.hpp > template < typename _Type, typename _Tcontainer = CList<_Type> > class mfc_clist_iterator : public boost::iterator_facade < mfc_clist_iterator < _Type, _Tcontainer >, _Type, boost::bidirectional_traversal_tag > { _Type m_node; // element type _Tcontainer* m_pCont; // Pointer to underlying CList container POSITION m_pos; // current CList position public: /////////////////////////////////////////////// /// /////////////////////////////////////////////// explicit mfc_clist_iterator(_Tcontainer* pCont) : m_node(0) , m_pos(0) , m_pCont(pCont) {} /////////////////////////////////////////////// /// Point to first element in CList /////////////////////////////////////////////// mfc_clist_iterator& begin() { m_pos = m_pCont->GetHeadPosition(); if(m_pos == 0) // safety checks *this = end(); else m_node = m_pCont->GetNext(m_pos); return *this; } /////////////////////////////////////////////// /// just call private constructor /////////////////////////////////////////////// mfc_clist_iterator end() { return mfc_clist_iterator(); } private: friend class boost::iterator_core_access; /////////////////////////////////////////////// /// constructs end() /////////////////////////////////////////////// mfc_clist_iterator(): m_node(0) , m_pos(0) , m_pCont(0) { } /////////////////////////////////////////////// /// Increment CList /////////////////////////////////////////////// void increment() { if(m_pos == NULL)// safety checks { *this = end(); return; } m_node = m_pCont->GetNext(m_pos); } /////////////////////////////////////////////// /// Decrement CList /////////////////////////////////////////////// void decrement() { if(m_pos == NULL)// safety checks { *this = end(); return; } m_node = m_pCont->GetPrev(m_pos); } /////////////////////////////////////////////// /// /////////////////////////////////////////////// bool equal(mfc_clist_iterator const & other) const { return m_pCont == other.m_pCont && m_node == other.m_node && m_pos == other.m_pos; } _Type& dereference() const { return (_Type&)m_node; } };
CList
的测试应用程序
#include < boost/iterator/iterator_facade.hpp > #include < algorithm > void print_int(int i) { cout << i << endl; } int main(int argc, TCHAR* argv[], TCHAR* envp[]) { CList < int > list_of_ints; // Add 10 integers to CList for(int i=0; i<10; i++) list_of_ints.AddTail(i); cout << "CList" << endl; mfc_clist_iterator < int > it(&list_of_ints); for_each(it.begin(), it.end(), print_int); return 0; }
提升 CView 迭代器
你最终会遵循相同的模式来提升其他类型的集合。
#include < boost/iterator/iterator_facade.hpp > // CView class view_iterator : public boost::iterator_facade< view_iterator, CView, boost::forward_traversal_tag > { CView* m_node; POSITION m_pos; CDocument* m_pDoc; public: /////////////////////////////////////////////// /// /////////////////////////////////////////////// explicit view_iterator(CDocument* pDoc) : m_node(0) , m_pos(0) , m_pDoc(pDoc) {} /////////////////////////////////////////////// /// /////////////////////////////////////////////// view_iterator& begin() { m_pos = m_pDoc->GetFirstViewPosition(); m_node = m_pDoc->GetNextView(m_pos); return *this; } /////////////////////////////////////////////// /// /////////////////////////////////////////////// view_iterator end() { return view_iterator(); } private: friend class boost::iterator_core_access; /////////////////////////////////////////////// /// for end() /////////////////////////////////////////////// view_iterator() : m_node(0) , m_pos(0) , m_pDoc(0) {} /////////////////////////////////////////////// /// /////////////////////////////////////////////// void increment() { if(m_node == 0) { m_pos = m_pDoc->GetFirstViewPosition(); m_node = m_pDoc->GetNextView(m_pos); } else m_node = m_pDoc->GetNextView(m_pos); } /////////////////////////////////////////////// /// /////////////////////////////////////////////// bool equal(view_iterator const& other) const { return this->m_node == other.m_node; } /////////////////////////////////////////////// /// /////////////////////////////////////////////// CView& dereference() const { return *m_node; } };
在文档视图应用程序中,你可以这样测试这段代码:
// In CDocument derived class view_iterator it = view_iterator(this).begin(); while(*it) { // Repaint the view it->SendMessage(WM_PAINT); it++; }
同样,使用下面的 CDocTemplate
迭代器:
doc_templ_iterator it = doc_templ_iterator(AfxGetApp()).begin(); // Autosave feature while(*it) { it->SaveAllModified();//Save all documents that were modified. it++; }
提升的 CDocTemplate 迭代器
// CDocTemplate class doc_templ_iterator : public boost::iterator_facade< doc_templ_iterator, CDocTemplate, boost::forward_traversal_tag > { CDocTemplate* m_node; POSITION m_pos; CWinApp* m_pApp; public: /////////////////////////////////////////////// /// /////////////////////////////////////////////// explicit doc_templ_iterator(CWinApp* pApp) : m_node(0) , m_pos(0) , m_pApp(pApp) {} /////////////////////////////////////////////// /// /////////////////////////////////////////////// doc_templ_iterator& begin() { m_pos = m_pApp->GetFirstDocTemplatePosition(); m_node = m_pApp->GetNextDocTemplate(m_pos); return *this; } /////////////////////////////////////////////// /// /////////////////////////////////////////////// doc_templ_iterator end() { return doc_templ_iterator(); } /////////////////////////////////////////////// /// /////////////////////////////////////////////// CDocTemplate* operator*() { return m_node; } CDocTemplate* operator->() { return m_node; } private: friend class boost::iterator_core_access; /////////////////////////////////////////////// /// for end() /////////////////////////////////////////////// doc_templ_iterator() : m_node(0) , m_pos(0) , m_pApp(0) {} /////////////////////////////////////////////// /// /////////////////////////////////////////////// void increment() { if(m_pos == 0) { *this = end(); return; } else m_node = m_pApp->GetNextDocTemplate(m_pos); } /////////////////////////////////////////////// /// /////////////////////////////////////////////// bool equal(doc_templ_iterator const& other) const { return this->m_node == other.m_node; } /////////////////////////////////////////////// /// /////////////////////////////////////////////// CDocTemplate& dereference() const { return *m_node; } };
泛化的好处
我将重点介绍将 MFC 集合提升为 STL 迭代器的众多好处之一。考虑一个动态分配的指针的 CArray
和 CList
。
// Allocate CArray of pointers CArray < CMyClass* > array_of_my_class_ptrs; for(int i=0; i<10; i++) array_of_my_class_ptrs.Add(new CMyClass); // Allocate CList of pointers CList < CMyOtherObj* > list_of_stuff; for(int i=0; i<10; i++) list_of_stuff.AddTail(new CMyOtherObj);
完成后,你需要在最后手动遍历每个容器,使用 delete
运算符删除每个指针。请注意,CArray
和 CList
的迭代语义是不同的。因此,你需要为删除逻辑实现两种不同的实现。
// implementation of delete CArray pointers for(int i=0; i< array_of_my_class_ptrs.GetSize(); i++) delete array_of_my_class_ptrs.GetAt(i); // implementation of delete for CList while(list_of_stuff.GetCount() > 0) delete list_of_stuff.RemoveHead();
使用提升的 CArray
和 CList
迭代器,你可以编写一个统一的删除 functor(取自 Scott Meyers 的《Effective STL》第 7 条):
struct DeleteObject { template < typename T > void operator () (const T* ptr) { delete ptr; ptr = 0; } };
现在,你可以这样进行删除:
mfc_carray_iterator itA(&array_of_my_class_ptrs); mfc_clist_iterator itL(&list_of_stuff); // Delete all pointers in both CArray and CList for_each(itA.begin(), itA.end(), DeleteObject()); for_each(itL.begin(), itL.end(), DeleteObject());
关注点
如果你注意到,这里展示的所有迭代器都可以进一步提升。我将把这个练习留给读者。
从现在开始,MFC 集合迭代器可以以非常通用统一的方式使用。此外,在大量的自定义或 STL 算法(如排序或搜索等)中都可以使用。我们还实现了安全检查,如果你使用 ++
或 --
运算符。一旦达到序列的末尾,迭代器就会重置到 end()
状态,因此在与 end()
函数进行比较时会评估为 TRUE
。
致谢
- 非常感谢 Boost 网站 和 boost::iterator 库的作者。
- 迭代器库链接
- 泛型编程链接
- Scott Meyers 的《Effective STL》
Bug
我没有在 MS VC 6.0 中测试过这段代码,但在 Visual Studio 6.0 SP6 上,编译这段代码很可能会遇到问题。该版本中的编译器会错误地解析代码:
template < typename _T, typename _Tcont=CArray<_T>>
它会假定粗体代码块是移位运算符(天哪!)。你需要在此处添加空格,如下所示:
template < typename _T, typename _Tcont=CArray<_T> >
历史
- 2008 年 10 月 20 日。初稿。