枚举迭代和字符串转换
枚举迭代和字符串转换
引言
是否曾想过遍历一系列枚举值?我曾遇到过这样的情况:给出一个枚举序列,需要执行特定任务。几分钟后,我生成了以下代码片段。它简单而有效。
enum MyEnum { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF };
const MyEnum& operator++(MyEnum& eValue) { return (eValue = (MyEnum)(eValue+1)); }
for(MyEnum eValue = firstEnum ; eValue < totalEnums; ++eValue)
DoSomething(eValue);
然而,这种实现存在一些根本性问题
- 它只适用于简单的顺序枚举。
- 它需要有关枚举序列的额外知识,即起始点和结束点。
除了遍历之外,`string` 转换功能也很有用,即能够将 `string` 转换为枚举值,反之亦然。
以下短文旨在提供一种实现此功能的简单机制。
EnumClass
EnumClass<>
提供了介绍中确定的功能。它为 C++ 风格的枚举提供了遍历和 `string` 转换功能。该类需要两个辅助宏来声明和实现枚举以及辅助类本身,但只需以下三行简单的代码,即可定义和实现 `enum` 和辅助类。
// Place in the header file for public access to enum MyEnum
// and the helper class MyEnumClass
#define MYENUM { enum1, firstEnum=enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF }
DECLARE_ENUM(MyEnumClass, MyEnum, MYENUM);
// Place in source file for implementation of MyEnumClass helper class
IMPLEMENT_ENUM(MyEnumClass);
上面的代码生成以下内容(为清晰起见,已删除 `MyEnumClass` 的外部声明)
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3,
enum4, enum5,totalEnums, errorEnum = 0xFF }
MyEnum__EnumClass MyEnumClass;
枚举 `MyEnum` 可如下使用
// conversion functionality
// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl;
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl;
// simple looping
for(MyEnums eValue = firstEnum; eValue <= totalEnums; ++eValue)
DoSomething(eValue);
更复杂的枚举
`for` 循环实现将正确地遍历顺序枚举,它还将处理非连续枚举,即。
enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=2, enum3=4,
enum4=8, enum5 = 16, totalEnums = enum5, errorEnum = 0xFF }
然而,简单的 `for` 循环无法保证处理非连续枚举(请参阅后面的注意事项),例如。
enum MyEnum { firstEnum = 0, enum1 = firstEnum, enum2=16, enum3=1, enum3=2,
enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }
为了解决这个问题,EnumClass<>
提供了可以正确遍历这些更复杂枚举序列的迭代器。以下代码片段展示了如何使用 EnumClass<>::const_iterator
来遍历更复杂的枚举序列。
// iteration
EnumClass<MyEnum>::const_iterator iter = MyEnumsEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumsEnumClass.end();
while(iter != end)
{
DoSomething(*iter);
iter++;
};
实现
以下是对 EnumClass
和辅助宏 DECLARE_ENUM
& IMPLEMENT_ENUM
的简要描述。
DECLARE_ENUM
此宏声明了枚举、递增运算符和辅助类的 `extern` 引用。
#define DECLARE_ENUM(EnumClassName, EnumName, ENUM) \
enum EnumName ENUM; \
class EnumClassName##__EnumClass: public EnumClass<MyEnum> \
{ \
public: \
EnumClassName##__EnumClass(): EnumClass<MyEnum>(DEFINE_AS_STR(ENUM)) {}; \
}; \
extern EnumClassName##__EnumClass EnumClassName; \
inline const EnumName& operator++(EnumName& eValue)
{ return (eValue = EnumClassName.GetNext(eValue)); } \
inline const EnumName& operator++(EnumName& eValue, int)
{ return (eValue = EnumClassName.GetNext(eValue)); }
- 第一个参数是辅助类对象的名称 -
EnumClassName
。extern EnumName##__EnumClass EnumClassName;
- 第二个参数是枚举序列的名称 -
EnumName
enum EnumName ENUM
- 第三个参数是枚举集 -
ENUM
enum EnumName ENUM
这将生成以下代码(使用上面的示例)
extern MyEnum__EnumClass MyEnumClass;
enum MyEnum { enum1, firstEnum= enum1, enum2, enum3, enum4, enum5,
totalEnums, errorEnum = 0xFF }
类 MyEnum__EnumClass
也是作为宏的一部分定义的。这个类是 EnumClass<>
的一个包装器,它只是用作向 EnumClass<>
提供字符串化枚举的机制。它不需要用于访问任何 `enum` 功能。有关字符串化枚举的更多信息,请参阅 DEFINE_AS_STR
部分。
可以通过类对象名称访问 `enum` 转换功能,即对于上面的示例
// convert enum to string - outputs "totalEnums"
std::cout << MyEnumClass(totalEnums) << std::endl;
// convert string to enum - outputs 5
std::cout << MyEnumClass("totalEnums") << std::endl;
可以通过基类名称和类对象访问 `enum` 迭代器,即。
EnumClass<MyEnum>::const_iterator iter = MyEnumClass.begin();
EnumClass<MyEnum>::const_iterator end = MyEnumClass.end();
IMPLEMENT_ENUM
此宏实例化辅助类。
#define IMPLEMENT_ENUM(EnumClassName) \
EnumClassName##__EnumClass EnumClassName;
- 第一个参数是辅助类对象的名称 -
EnumClassName
。EnumName##__EnumClass EnumClassName;
EnumClass
该类为任何枚举序列提供了转换和遍历功能。
它是一个模板类,提供基于唯一枚举标识符的命名空间解析,即为每个枚举生成一个不同且唯一的 EnumClass<>
,从而防止命名空间冲突。EnumClass<>
的接口很简单。
// Class construction
EnumClass(const std::string& eString)
:undefined("undefined") { ParseEnumString(eString); }
// Conversion
const std::string operator()(T eValue) const { return GetEnumString(eValue); }
const T operator()(const std::string& sStr) const { return GetEnumValue(sStr); }
// iteration
const_iterator begin() const { return const_iterator(enumList.begin()); }
const_iterator end() const { return const_iterator(enumList.end()); }
const_reverse_iterator rbegin() const
{ return const_reverse_iterator(enumList.rbegin()); }
const_reverse_iterator rend() const
{ return const_reverse_iterator(enumList.rend()); }
// first / last accessors
const T first() const { return enumList.front(); }
const T last() const { return enumList.last(); }
size_t size() const { return enumList.size(); }
EnumClass<>
持有一个 EnumInfo
对象的向量。这些对象包含枚举值与 `string` 等价物之间的映射。EnumInfo
对象列表在类构造期间生成。ParseEnumString()
方法将枚举 `string` 分割成单独的名称、值对。名称存储为 `enum` 的名称,而值被评估以生成枚举的整数值。然后将这些名称值对添加到 EnumInfo
对象列表中。如果 EnumInfo
对象已存在相同的值,则将名称添加到现有的 EnumInfo
对象中,并丢弃新的 EnumInfo
对象。
附加信息
枚举求值
如果未定义值,则赋给EnumInfo
对象的 `value` 是顺序的,从零开始。如果定义了值,则对其进行求值并赋给 EnumInfo
对象。求值逻辑允许简单的代数表达式(包括使用大括号)。可以处理以下运算符- + 加
- - 减
- * 乘
- / 除
- ^ 异或
- & 算术与
- | 算术或
- % 模
求值应该能处理 99% 的表达式,但是求值严格按照从左到右进行,即不执行优先级,有关详细信息请参阅注意事项。
枚举值
#define ENUM
为了确保 `enum` 序列(多个逗号,因此多个参数)使用标准宏正常工作,需要固定数量的宏参数。这是通过将 `enum` 序列定义为宏来实现的。然后,此 `enum` 宏可以在任何使用它的地方被视为单个宏参数。宏的主体中可以包含任意数量的逗号来定义枚举序列,并仍然将其视为单个宏参数。
DEFINE_AS_STR
在尝试字符串化 ENUM #define
时遇到了一个问题。编译器似乎不执行预期的 #define
替换,而是对 #define
名称进行字符串化,而不是其内容。
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = #P1;
生成以下内容
char* myString = "QAZ";
DEFINE_AS_STR
允许编译器执行正确的替换,即。
#define QAZ { 1,2,3,4,5 }
#define ZAQ(P1) char* myString = DEFINE_AS_STR(P1);
生成以下内容
char* myString = "{ 1,2,3,4,5 }";
我不能声称发现了这个宝藏,这个想法是从 boost
库中借鉴的。所以我不太确定它是如何工作的,但它确实确保了正确的替换。
注意事项
for 循环
前置和后置递增运算符可以正常工作,但是应该注意的是,如果将它们与非顺序枚举一起使用,可能会出现意外结果。考虑以下情况
enum MyEnums { firstEnum = 0, enum1 = firstEnum, enum2=1, enum3=16, enum3=2,
enum4=4, enum5 = 8, totalEnums = enum5, errorEnum = 0xFF }
for(MyEnums eValue = firstEnum; eValue < totalEnums; ++eValue)
DoSomething(eValue)
递增运算符将按以下顺序遍历 `enum`:`enum1`、`enum2`、`enum3`。但是,当 `eValue` 变为 `enum3`(`eValue = 2`)时,它小于 `totalEnums`,即 16 > 8,`for` 循环将退出。有两种解决方案:首先,不要使用 `<` 运算符,而是使用 `!=` 运算符,或者使用 EnumClass
提供的 const_iterator
。注意:使用 `!=` 运算符需要结束值是唯一的。在遍历非顺序 `enum` 时,应谨慎确保 `for` 循环按预期工作。
表达式求值
EnumClass
使用一个非常简单的求值机制,该机制按从左到右的顺序进行求值。它不对值表达式执行任何优先级,因此
A+B*C 将不会正确求值,请使用大括号确保正确求值,即 (A+B)*C
结论
我曾尝试在此实现中使用标准的 C++,但仅使用 Microsoft 编译器进行了编译,因此,如果它能成功与其他编译器/平台编译,我会很有兴趣知道。我怀疑字符串化宏会有一个问题,但比我更聪明的人应该能够解决这个问题。
上面确定的两个注意事项是为将来的版本准备的;我不确定 `for` 循环问题是否有简单的答案,但是如果需求足够,表达式求值应该是可以解决的。
希望这个类能有所帮助。祝您使用愉快!
历史
- 2008年10月06日:原始文章