65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 Boost iterator_facade 将 MFC 集合包装成符合 STL 标准的迭代器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.56/5 (21投票s)

2008 年 10 月 20 日

CPOL

5分钟阅读

viewsIcon

60038

downloadIcon

531

如何使用 Boost iterator_facade 将 MFC 集合包装成符合 STL 的迭代器。

引言

本文需要来自 Boost 网站 的 Boost iterator 头文件。这里描述的方法可以应用于任何集合,不仅仅是 MFC。但是,为了本文的目的,我将展示如何为 MFC 集合类实现符合 STL 的迭代器。

背景

迭代器提供统一的访问方法来访问在实现细节上不相关的集合。这不仅适用于“实现细节”,也适用于整个概念。例如,可以为目录和文件结构创建迭代器(查看 Boost 文件系统库,位于 此处)。你可以为任何代表“多”的事物编写迭代器。例如,监视器、串行端口、TCP 连接、XML 节点、HTTP 标签等等。此外,迭代器使得底层集合能够被标准模板库(STL)提供的算法(如 for_eachsearchfindfind_iffind_endfind_first_ofcountcount_ifequalsearch_nsort 等)或需要标准迭代器的第三方库所操作。

MFC 集合主要分为三大类:索引数组CArrayCPtrArrayCObArrayCTypedPtrArrayCStringArrayCByteArrayCDWordArrayCWordArrayCUIntArray)、链表CListCPtrListCObListCTypedPtrListCStringList)和映射CMapCMapPtrToWordCMapPtrToPtrCMapStringToObCMapStringToPtrCMapStringToStringCMapWordToObCMapWordToPtr)。每种类别的遍历/访问方式都不同,因此需要根据所使用的集合进行特定的实现。请看以下示例:

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 迭代器的众多好处之一。考虑一个动态分配的指针的 CArrayCList

// 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 运算符删除每个指针。请注意,CArrayCList 的迭代语义是不同的。因此,你需要为删除逻辑实现两种不同的实现。

// 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();

使用提升的 CArrayCList 迭代器,你可以编写一个统一的删除 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

致谢

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 日。初稿。
© . All rights reserved.