面向对象的枚举






4.62/5 (20投票s)
2005年2月2日
4分钟阅读

71993

410
关于使用宏封装枚举的文章。
引言
C++ 中的枚举相当简单。然而,在一个被设计为强类型(strongly-typed)的语言中,对枚举类型例外处理似乎不一致。出于某些原因,枚举本质上被当作常量整数处理。这就是我试图纠正的。
我见过几种尝试将枚举封装到类中的方法;然而,我还没有看到一个简单的实现。其中一些需要使用编译过的工具来创建类。我想要一个允许我使用 C++ 宏来编写我的枚举的实现。
使用代码
我将宏定义分开了,使其类似于编写一个类。因此,定义宏必须先使用(例如,在头文件中);然而,实现宏可以在定义之后立即使用,或者在源文件中使用。
以下是从演示项目中摘录的一个例子
// from MyEnums.h #include "Enumeration.h" // the EWeekdays definition BEGIN_ENUM_DEFINITION(Weekdays) DEFINE_ENUM(SUNDAY) DEFINE_ENUM(MONDAY) DEFINE_ENUM(TUESDAY) DEFINE_ENUM(WEDNESDAY) DEFINE_ENUM(THURSDAY) DEFINE_ENUM(FRIDAY) DEFINE_ENUM(SATURDAY) END_ENUM_DEFINITION()
// from MyEnums.cpp #include "MyEnums.h" // the EWeekdays implementation BEGIN_ENUM_IMPLEMENTATION(Weekdays) ENUM_ENTRY(EWeekdays::SUNDAY, "Sunday") ENUM_ENTRY(EWeekdays::MONDAY, "Monday") ENUM_ENTRY(EWeekdays::TUESDAY, "Tuesday") ENUM_ENTRY(EWeekdays::WEDNESDAY, "Wednesday") ENUM_ENTRY(EWeekdays::THURSDAY, "Thursday") ENUM_ENTRY(EWeekdays::FRIDAY, "Friday") ENUM_ENTRY(EWeekdays::SATURDAY, "Saturday") END_ENUM_IMPLEMENTATION(Weekdays)
上面的代码创建了一个名为 EWeekdays
的类,该类封装了一个名为 Weekdays
的枚举,其中包含星期名称作为常量。
文档
BEGIN_ENUM_DEFINITION(theEnumName)
这个宏将创建一个名为
theEnumName
的类,其中包含一个名为theEnumName
的嵌套enum
声明。它还声明了一个结构体来充当映射。这个映射允许将枚举值转换为可读的字符串值。最后,它还声明了几个成员函数。DEFINE_ENUM_VALUE(enumName, enumValue)
这个宏将添加一个名为
enumName
的枚举值,并将其值设置为enumValue
。DEFINE_ENUM(enumName)
这个宏将添加一个名为
enumName
的枚举值。DEFINE_LAST_ENUM(enumName)
这个宏与
DEFINE_ENUM
宏相同,但它会省略末尾的逗号。在大多数编译器上您不需要使用它,但从技术上讲,这是 ANSI 定义枚举类型声明的方式。END_ENUM_DEFINITION
这个宏结束了新枚举类的声明。
BEGIN_ENUM_IMPLEMENTATION(theEnumName)
这个宏开始了新枚举类的实现。
theEnumName
必须与BEGIN_ENUM_DEFINITION
宏中给出的名称匹配。ENUM_ENTRY(enumValue, enumName)
这个宏向名称和值的映射添加一个条目。它会将
enumName
中的字符串值映射到enumValue
整数值。它还会为映射的eData
成员赋值 0。ENUM_ENTRY_DATA(enumValue, enumName, enumData)
这个宏以与
ENUM_ENTRY
宏相同的方式向映射添加一个条目;然而,它允许您将数据成员设置为一个unsigned long
值。也就是说,它可以是您应用程序的有意义的值,也可以是指向另一个结构的指针。请记住,映射是静态的,所以如果您将此值设置为指针,请确保它指向的对象在您的枚举对象有效期间(例如,在作用域内)都是有效的。END_ENUM_IMPLEMENTATION(theEnumName)
这个宏完成了实现。
theEnumName
中的值必须与BEGIN_ENUM_IMPLEMENTATION
中的值匹配。
示例
以下是宏所做事情的示例
BEGIN_ENUM_DEFINITION(Weekdays) DEFINE_ENUM(SUNDAY) DEFINE_ENUM(MONDAY) DEFINE_ENUM(TUESDAY) DEFINE_ENUM(WEDNESDAY) DEFINE_ENUM(THURSDAY) DEFINE_ENUM(FRIDAY) DEFINE_ENUM(SATURDAY) END_ENUM_DEFINITION()
此声明创建了以下定义
class EWeekdays { public: enum Weekdays; protected: Weekdays m_eCurrentValue; public: struct SEnumMap { Weekdays eVal; unsigned long eData; const char* eName; }; typedef SEnumMap* Position; typedef SEnumMap* EnumItem; protected: static SEnumMap ms_EnumMap[]; public: EWeekdays() { m_eCurrentValue = (Weekdays)0; } EWeekdays(const EWeekdays& eVal) { *this = eVal; } EWeekdays(const int& iVal) { m_eCurrentValue = (Weekdays)iVal; } const EWeekdays& operator= (const EWeekdays& eVal) { m_eCurrentValue = eVal.m_eCurrentValue; return *this; } const EWeekdays& operator= (const int& eVal) { m_eCurrentValue = (Weekdays)eVal; return *this; } bool operator==(const EWeekdays& eVal) const { return m_eCurrentValue == eVal.m_eCurrentValue; } bool operator!=(const EWeekdays& eVal) const { return m_eCurrentValue != eVal.m_eCurrentValue; } bool operator< (const EWeekdays& eVal) const { return m_eCurrentValue < eVal.m_eCurrentValue; } bool operator> (const EWeekdays& eVal) const { return m_eCurrentValue > eVal.m_eCurrentValue; } bool operator<=(const EWeekdays& eVal) const { return (*this < eVal || *this == eVal); } bool operator>=(const EWeekdays& eVal) const { return (*this > eVal || *this == eVal); } bool operator==(const int& iVal) const { return m_eCurrentValue == iVal; } bool operator!=(const int& iVal) const { return m_eCurrentValue != iVal; } bool operator< (const int& iVal) const { return m_eCurrentValue < iVal; } bool operator> (const int& iVal) const { return m_eCurrentValue > iVal; } bool operator<=(const int& iVal) const { return (*this < iVal || *this == iVal); } bool operator>=(const int& iVal) const { return (*this > iVal || *this == iVal); } operator int() const { return (int)m_eCurrentValue; } operator const char*() const { return GetEnumName(); } const char* GetEnumName() const { return GetEnumName(*this); } static const char* GetEnumName(const int& iVal) { return GetEnumName((EWeekdays)iVal); } unsigned long GetEnumData() { return GetEnumData(*this); } static unsigned long GetEnumData(const int& iVal) { return GetEnumData((EWeekdays)iVal); } static bool IsValidEnum(const int& iVal) { return IsValidEnum((EWeekdays)iVal); } bool IsValidEnum() { return IsValidEnum(*this); } static const char* GetEnumName(const EWeekdays& eVal); static unsigned int GetCount(); static unsigned long GetEnumData(const EWeekdays& eVal); static EnumItem FindName(const char* name); static bool IsValidEnum(const EWeekdays& eVal); static Position GetFirstEnumPosition(); static const EnumItem GetNextEnumPosition(Position& pos); enum Weekdays { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, }; };
您应该注意到,静态映射 ms_EnumMap
是未指定大小的,并且 static const char* GetEnumName(const EWeekdays& eVal)
、static unsigned int GetCount()
、static unsigned long GetEnumData(const EWeekdays& eVal)
、static EnumItem FindName(const char* name)
、static bool IsValidEnum(const EWeekdays& eVal)
、static Position GetFirstEnumPosition()
和 static const EnumItem GetNextEnumPosition(Position& pos)
函数未实现。这些步骤通过下面的实现宏来完成。
BEGIN_ENUM_IMPLEMENTATION(Weekdays) ENUM_ENTRY(EWeekdays::SUNDAY, "Sunday") ENUM_ENTRY(EWeekdays::MONDAY, "Monday") ENUM_ENTRY(EWeekdays::TUESDAY, "Tuesday") ENUM_ENTRY(EWeekdays::WEDNESDAY, "Wednesday") ENUM_ENTRY(EWeekdays::THURSDAY, "Thursday") ENUM_ENTRY(EWeekdays::FRIDAY, "Friday") ENUM_ENTRY(EWeekdays::SATURDAY, "Saturday") END_ENUM_IMPLEMENTATION(Weekdays)
上面的一组宏实现了枚举类的最后几个步骤
EWeekdays::SEnumMap EWeekdays::ms_EnumMap[] = { { EWeekdays::SUNDAY, 0, "Sunday" }, { EWeekdays::MONDAY, 0, "Monday" }, { EWeekdays::TUESDAY, 0, "Tuesday" }, { EWeekdays::WEDNESDAY, 0, "Wednesday" }, { EWeekdays::THURSDAY, 0, "Thursday" }, { EWeekdays::FRIDAY, 0, "Friday" }, { EWeekdays::SATURDAY, 0, "Saturday" }, }; const char* EWeekdays::GetEnumName(const EWeekdays& eVal) { Position pos = GetFirstEnumPosition(); while (0 != pos) { EnumItem pVal = GetNextEnumPosition(pos); if (pVal->eVal == eVal) return pVal->eName; } return 0; } unsigned int EWeekdays::GetCount() { return (unsigned int)(sizeof(ms_EnumMap) / sizeof(ms_EnumMap[0])); } unsigned long EWeekdays::GetEnumData(const EWeekdays& eVal) { Position pos = GetFirstEnumPosition(); while (0 != pos) { EnumItem pVal = GetNextEnumPosition(pos); if (pVal->eVal == eVal) return pVal->eData; } return 0; } EWeekdays::EnumItem EWeekdays::FindName(const char* name) { Position pos = GetFirstEnumPosition(); while (0 != pos) { EnumItem pVal = GetNextEnumPosition(pos); if (0 == strcmp(name, pVal->eName)) return pVal; } return 0; } bool EWeekdays::IsValidEnum(const EWeekdays& eVal) { Position pos = GetFirstEnumPosition(); while (0 != pos) { EnumItem pVal = GetNextEnumPosition(pos); if (pVal->eVal == eVal) return true; } return false; } EWeekdays::Position EWeekdays::GetFirstEnumPosition() { return &ms_EnumMap[0]; } const EWeekdays::EnumItem EWeekdays::GetNextEnumPosition(EWeekdays::Position& pos) { EnumItem ret = pos; if (pos < &ms_EnumMap[GetCount() - 1]) pos++; else pos = 0; return ret; }
关注点
您应该注意到,您可以创建多个具有相同整数值的 enum
条目;然而,当使用 (const char*)
操作符获取它们的名称时,它将返回找到的第一个名称对应的字符串。如果您创建了多个条目,您也应该为它们使用相同的字符串。
此修订版不再受限于必须从 0 开始的限制。现在,您不仅可以从任何地方开始,枚举值也不必是连续的。
// from MyEnums.h // the EFoodGroups defintion BEGIN_ENUM_DEFINITION(FoodGroups) DEFINE_ENUM_VALUE(GRAIN, -2) DEFINE_ENUM_VALUE(MEAT, 4) DEFINE_ENUM_VALUE(FRUIT, 9) DEFINE_ENUM(VEGETABLE) END_ENUM_DEFINITION()
上面的代码声明了一个枚举,其值为 -2、4、9 和 10。为了适应这一点,我添加了 GetFirstEnumerationPosition
和 GetNextEnumerationPosition
函数。如果您使用这种方式定义枚举,您将需要使用这些函数来迭代枚举。如果您的数字是连续的,您仍然可以使用类似 for
的循环。
由于你们中的一些人要求一种从字符串到枚举的方法,我想出了一种方法来适应这种能力,而不会让它过于麻烦。FindName
成员函数将遍历枚举并尝试查找名称匹配项(请记住,它会在找到的第一个匹配项上停止)。如果成功,它将返回一个 EnumItem
(它是 SEnumMap*
的 typedef
)。如果失败,它将返回 0。我意识到这距离字符串的赋值运算符还有一步之遥,但我需要一种方法来进行错误检查。要使用此方法进行赋值,您将执行以下操作:
EWeekday Day = EWeekday::SUNDAY; const char* MyDay = "Saturday"; EWeekdays::EnumItem item = EWeekdays::FindName(MyDay); if (0 != item) { Day = item->eVal; } else { // not found, do something here }
您还会注意到添加了三个 IsEnumValid
成员函数。这些函数允许您检查某个值是否对该枚举有效。
历史
- 2005/01/30 - 原始版本。
- 2005/02/04 - 上传了正确的示例代码并添加了符合 ANSI 标准的定义宏。
- 2005/02/09 - 添加了一些重要的功能(映射的数据成员、名称搜索、迭代函数、有效性检查)。