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

使 MFC 集合能在基于范围的 for 循环中工作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (22投票s)

2014年10月30日

CPOL

4分钟阅读

viewsIcon

26607

MFC Collection Utilities 是一个小型开源库,可让您将任何 MFC 集合与范围 for 循环一起使用。

本文介绍了如何在 C++11 中使 MFC 集合与范围 for 循环一起使用。MFC 可能不是新项目的首选框架,但有许多基于 MFC 的应用程序需要维护。使 MFC 集合能够与范围 for 循环一起工作,这是现代化旧代码的一个优势。Codeplex 上提供了一个名为 MFC Collection Utilities 的开源库,提供了此功能。

引言

遍历 MFC 集合元素的标准方法是使用索引。

CStringArray strarr;
strarr.Add("one");
strarr.Add("two");
strarr.Add("three");
for(INT_PTR i = 0; i < strarr.GetSize(); ++i)
{
   CString temp = strarr.GetAt(i); // could also be CString const &
}

这里的索引并不重要。它只是一个临时变量,允许您访问元素。

对于具有随机访问的 C++ 标准容器,同样适用。

std::vector<int> v = {1, 2, 3};
for(size_t i = 0; i < v.size(); ++i)
{
   int value = v[i];
}

当然,遍历 C++ 标准容器元素的通用方法是使用迭代器。

std::vector<int> v = {1, 2, 3};
for(std::vector<int>::iterator i = std::begin(v); i != std::end(v); ++i)
{
   int value = *i;
}

无论它是数值还是迭代器,在大多数循环中都不是必需的。许多语言提供了 foreach 循环语义,不需要索引或迭代器。C++11 已添加了此功能,称为范围 for 循环。

使用范围 for 循环,最后一个示例可以这样写:

std::vector<int> v = {1, 2, 3};
for(auto value : v)
{
   // do something with value
}

但是,如果我们将其应用于第一个示例中的 CStringArray,我们会收到错误。

CStringArray strarr;
strarr.Add("one");
strarr.Add("two");
strarr.Add("three");
for(auto const & temp : strarr)
{
}
1>error C3312: no callable 'begin' function found for type 'CStringArray'
1>error C3312: no callable 'end' function found for type 'CStringArray'

原因在于范围 for 循环只是语法糖,编译器会将其转换为其他内容。通用表达式...

for ( range_declaration : range_expression ) loop_statement

...被转换为以下内容:

{
   auto && __range = range_expression ; 
   for (auto __begin = begin_expr, __end = end_expr; 
        __begin != __end;
        ++__begin)
   {
      range_declaration = *__begin;
      loop_statement 
   } 
}

begin_exprend_expr 的规则因范围的类型而异:

  • 对于类 C 数组:__range__range + __bound
  • 对于具有 begin()end() 成员的类类型:__range.begin()__range.end()
  • 对于其他类型:begin(__range)end(__range)

这解释了 CStringArray 示例中的错误。MFC 中没有可用的 begin(CStringArray)end(CStringArray)

使 MFC 集合能在基于范围的 for 循环中工作

要使 MFC 集合能够与范围 for 循环一起使用,我们需要两样东西:

  • 实现 operator++operator*operator!= 的集合迭代器。
  • 每个集合的非成员函数 begin()end(),它们返回相应的开始和结束迭代器。

对于 CStringArray 集合,可以这样实现:

class CStringArrayIterator
{
public:
   CStringArrayIterator(CStringArray& collection, INT_PTR const index):
      m_index(index),
      m_collection(collection)
   {
   }

   bool operator!= (CStringArrayIterator const & other) const
   {
      return m_index != other.m_index;
   }

   CString& operator* () const
   {
      return m_collection[m_index];
   }

   CStringArrayIterator const & operator++ ()
   {
      ++m_index;
      return *this;
   }

private:
   INT_PTR        m_index;
   CStringArray&  m_collection;
};

inline CStringArrayIterator begin(CStringArray& collection)
{
   return CStringArrayIterator(collection, 0);
}

inline CStringArrayIterator end(CStringArray& collection)
{
   return CStringArrayIterator(collection, collection.GetCount());
}

定义了这些之后,如果您在此文章中运行第一个示例,其中包含 CStringArray,它将正常工作。您可以在重载的运算符中设置断点,并观察在迭代容器元素时它们是如何被调用的。

但是,如果您执行以下代码,它将再次引发错误:

void Func(CStringArray const & strarr)
{
   for(auto const & temp : strarr)
   {

   }
}

问题在于范围的类型现在是 const CStringArray,并且没有适用于它的 begin()end() 函数。因此,您还需要定义常量迭代器以及适用于常量集合并返回相应迭代器的 begin()end() 函数。

MFC Collection Utilities 库

MFC Collection Utilities 是一个小型开源库(可在 Codeplex 上找到),可让您将所有 MFC 容器与范围 for 循环一起使用。

MFC 提供了一系列模板集合和非模板集合,该库为所有这些集合(包括常量和非常量类型)定义了适当的迭代器以及 begin()end() 函数。该库包含一个名为 mfciterators.h 的头文件,您需要在希望在范围 for 循环中使用 MFC 集合的源文件中包含它。

该库需要 Visual Studio 2012 或更高版本。VS2012 是第一个包含支持范围 for 循环的 C++ 编译器的 Visual Studio 版本。

#include "mfciterators.h"

void func(CStringArray const & arr)
{
   for(auto const & str : arr)
   {
      // do something with str
   }
}

MFC 提供了三种类型的集合:数组、列表和映射。对于映射,该库允许通过具有两个字段(keyvalue)的键值对来访问内容。

支持的模板集合

数组 列表 映射
CArray CList CMap
CTypedPtrArray CTypedPtrList CTypedPtrMap

支持的非模板集合

数组 列表 映射
CObArray CObList CMapPtrToWord
CByteArray CPtrList CMapPtrToPtr
CDWordArray CStringList CMapStringToOb
CPtrArray   CMapStringToPtr
CStringArray   CMapStringToString
CWordArray   CMapWordToOb
CUIntArray   CMapWordToPtr

请注意,某些模板集合实际上是类型安全的非模板集合类型的包装器,不能与其他类一起使用。

  • CTypedPtrArray 用于 CPtrArrayCObArray
  • CTypedPtrList 用于 CPtrList
  • CTypedPtrMap 用于 CMapPtrToPtrCMapPtrToWordCMapWordToPtrCMapStringToPtr

示例

数组

CStringArray arr;
arr.Add("this");
arr.Add("is");
arr.Add("a");
arr.Add("sample");

for(auto & s : arr)
{
   s.MakeUpper();
}
CArray<int> arr;
arr.Add(1);
arr.Add(2);
arr.Add(3);
arr.Add(4);

for(auto const n : arr)
{
   std::cout << n << std::endl;
}

列表

class CFoo
{
public:
   int value;

   CFoo(int const v): value(v) {}
};

CTypedPtrList<CPtrList, CBar*> ptrlist;
ptrlist.AddTail(new CFoo(1));
ptrlist.AddTail(new CFoo(2));
ptrlist.AddTail(new CFoo(3));

for(auto & o : ptrlist)
   o->value *= 2;
CList<int> list;
list.AddTail(1);
list.AddTail(2);
list.AddTail(3);

auto const & clist = list;
for(auto const n : clist)
{
   std::cout << n << std::endl;
}

映射

CMap<int, int, CString, CString> map;

map.SetAt(1, "one");
map.SetAt(2, "two");
map.SetAt(3, "three");

for(auto & kvp : map)
{
   kvp.value.MakeUpper();
}

for(auto const & kvp : map)
{
   CString temp;
   temp.Format("key=%d, value=%s", kvp.key, kvp.value);
}
CTypedPtrMap<CMapWordToPtr, WORD, CFoo*> map;

map.SetAt(1, new CFoo(1));
map.SetAt(2, new CFoo(2));
map.SetAt(3, new CFoo(3));

// do something with map

for(auto & kvp : map)
{
   delete kvp.value;
}

下载和使用该库

该库可在 Codeplex 上找到,最新版本可在此处下载:这里

为了简化部署到项目,提供了一个 nuget 包。您可以下载它,并使用 NuGet 包管理器直接从 Visual Studio 将其添加到您的项目中。

结论

C++11 中的范围 for 循环提供了 foreach 语义,使您无需使用索引或迭代器即可遍历范围/集合。MFC 集合不提供范围 for 循环所需的适当迭代器和函数,但可以轻松创建它们。

MFC Collection Utilities 是一个小型开源库,包含一个头文件,可让您在 Visual Studio 2012 或更高版本中使用任何 MFC 集合与范围 for 循环。

历史

  • 2014 年 10 月 30 日:初始版本
© . All rights reserved.