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






4.86/5 (22投票s)
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_expr
和 end_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 提供了三种类型的集合:数组、列表和映射。对于映射,该库允许通过具有两个字段(key
和 value
)的键值对来访问内容。
支持的模板集合
数组 | 列表 | 映射 |
CArray | CList | CMap |
CTypedPtrArray | CTypedPtrList | CTypedPtrMap |
支持的非模板集合
数组 | 列表 | 映射 |
CObArray | CObList | CMapPtrToWord |
CByteArray | CPtrList | CMapPtrToPtr |
CDWordArray | CStringList | CMapStringToOb |
CPtrArray | CMapStringToPtr | |
CStringArray | CMapStringToString | |
CWordArray | CMapWordToOb | |
CUIntArray | CMapWordToPtr |
请注意,某些模板集合实际上是类型安全的非模板集合类型的包装器,不能与其他类一起使用。
CTypedPtrArray
用于CPtrArray
和CObArray
CTypedPtrList
用于CPtrList
CTypedPtrMap
用于CMapPtrToPtr
、CMapPtrToWord
、CMapWordToPtr
和CMapStringToPtr
示例
数组
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 日:初始版本