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

面向对象的枚举

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.62/5 (20投票s)

2005年2月2日

4分钟阅读

viewsIcon

71993

downloadIcon

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。为了适应这一点,我添加了 GetFirstEnumerationPositionGetNextEnumerationPosition 函数。如果您使用这种方式定义枚举,您将需要使用这些函数来迭代枚举。如果您的数字是连续的,您仍然可以使用类似 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 - 添加了一些重要的功能(映射的数据成员、名称搜索、迭代函数、有效性检查)。
© . All rights reserved.