在C++中是否可能有枚举类型的枚举类型?

19

在C++中是否可以有枚举类型的枚举?

错误类型:

  • Type1
  • Type2
  • Type3

Type1:

  • cause1
  • cause2

Type2:

  • cause3
  • cause4

Type3:

  • cause5
  • cause6

每个枚举类型都是整数值。它们将用于通信协议栈。在接收端,接收器必须从接收到的值解码错误的类型和原因。如果无法使用枚举类型,则最好的方法是什么?


11
抱歉,答案是否定的——你需要找到一种不同的方法来做事情。一种典型的方法是将类型编码在数字的若干个高位上,将原因编码在数字的若干个低位上(例如,16位值,每8位表示一种)。 - Jerry Coffin
不可能有枚举的枚举,但你可以通过将类型和原因分别作为结构体的一部分或为每个字段分配特定的位来表示数据。 - Tuxdude
1
即使您可以拥有枚举的枚举,由于程序由两台不同的计算机运行,同一子枚举(例如Type1Cause1)的值可能会以不同的方式实例化。这不是很危险吗? - lucasg
4个回答

9

我甚至不确定“枚举的枚举”意味着什么。但通常处理这种情况的方法是在单个枚举中定义范围:

enum Errors
{
    type1 = 0x000,
    cause1,
    cause2,

    type2 = 0x100,
    cause3,
    cause4,
    ...
    causeMask = 0xFF,
    typeMask  = 0xFF00
};

或者简单地定义不同的枚举,用不同的单词,并使用unsigned(或unsigned short,或其他)和一些转换来处理不同的原因。

无论采用什么解决方案,我都会将其封装在一个类中,以便客户端代码只需要处理errorType()errorCause()errorCause()甚至可以是错误类型值的模板。(但是,您需要为每个类型值提供显式的特化,因为编译器否则将不知道如何将值映射到原因类型。)


对的,以某种方式将类型嵌入原因中是确保您不混淆错误类型和原因的唯一方法。这是最简单的方法。 - Yngve Hammersland

4

正如Jerry所说,直接实现是不可能的。解决方法之一是拥有两个枚举类型,一个用于类别,一个用于子类别。

然而,正如georgesl所说,在协议中这样做可能是危险的。你应该明确定义枚举值:

struct Error
{
   enum Type {
      UNKNOWNTYPE = 0,
      TYPE1 = 1,
      TYPE2 = 2,
      TYPE3 = 3
   };
   enum Subtype {
      UNKNOWNSUBTYPE = 0,
      // subtype for error type 1
      CAUSE1 = 1001,
      CAUSE2 = 1002,
      CAUSE3 = 1003,
      // subtype for error type 2
      CAUSE4 = 2001,
      CAUSE5 = 2002
   };

   Type type;
   Subtype subtype;
};

int main()
{
   Error error;
   error.type = Error::TYPE1;
   error.subtype = Error::CAUSE1;
}

请确保选择数字时明智,以便将来进行扩展。

更新:实际使示例功能正常。

替代方案,更加类型安全:

struct ErrorType
{
   enum type {
      UNKNOWNTYPE = 0,
      TYPE1 = 1,
      TYPE2 = 2,
      TYPE3 = 3
   };
};

struct ErrorSubtype
{
   enum type {
      UNKNOWNSUBTYPE = 0,
      // subtype for error type 1
      CAUSE1 = 1001,
      CAUSE2 = 1002,
      CAUSE3 = 1003,
      // subtype for error type 2
      CAUSE4 = 2001,
      CAUSE5 = 2002
   };
};

struct Error
{
   ErrorType::type type;
   ErrorSubtype::type subtype;
};

int main()
{
   Error error;
   error.type = ErrorType::TYPE1;
   error.subtype = ErrorSubtype::CAUSE1;
}

在这种情况下,我是否需要枚举类型,因为它已经被编码为子类型了? - sajas
但是这种设计的问题在于我可以将错误类型与错误的子类型相结合,也就是说,我可以将TYPE1与CAUSE4相耦合,对吗? - sajas
是的,没错。如果你想要进一步解耦,你可以创建三个结构体:Error、ErrorType和ErrorSubtype。然后让Error包含另外两个结构体。 - Arne
我已经添加了一个更加类型安全的解决方案。 - Arne
可以使用邪恶的宏来创建组合枚举值。这将防止使用错误的子类型。如果预处理器宏是一个选项,我可以给出一个答案... - sanosdole
显示剩余2条评论

3

我不建议这样做。最好使用明确的错误类型,包含有关错误的信息(您可以添加字符串等)。此外,这种方法不太安全。请参见James的回答。

但无论如何,这是邪恶的宏版本:

#define DECL_ERROR_TYPE(errorType, value) , errorType = value << 16
#define DECL_ERROR(errorType, cause, value) , errorType##_##cause = (errorType + value)

#define GET_ERROR_TYPE(error) (error & 0xFFFF0000)

enum Error
{
NoError = 0
DECL_ERROR_TYPE(Type1, 1)
DECL_ERROR(Type1, Cause1, 1)
DECL_ERROR(Type1, Cause2, 2)

DECL_ERROR_TYPE(Type2, 2)
DECL_ERROR(Type2, Cause1, 1)

DECL_ERROR_TYPE(Type3, 3)
DECL_ERROR(Type3, Cause1, 1)
DECL_ERROR(Type3, Cause2, 2)
};

这使得您可以像这样使用它:

Error err1 = Type1_Cause1;

if(Type1 == GET_ERROR_TYPE(err1))
    return 0; // Works

0

我实在无法忍受使用枚举。因此,我有另一种使用显式类型的答案。 它还不完整,但显示了正确的方向,并包含了添加描述等可能扩展。

这是声明的代码:

struct Error
{
public:
    struct ErrorType
    {
        int _code;
        ErrorType(int code) : _code(code << 16) {}
    };
private:
    friend struct Errors;
    ErrorType _type;
    int _code;

    Error(ErrorType type, int causeCode)
        : _type(type), _code(causeCode)
    {
    }

    static std::map<int, Error> _errors;
public:
    Error() : _type(-1), _code(-1) {}
    static Error FromCode(int code) { return _errors[code]; }

    bool IsOfType(const ErrorType& type )
    {
        return _type._code == type._code;
    }

    operator int()
    {
        return _code | _type._code;
    }

    bool operator == (Error const& other) const
    {
        return _code == other._code && _type._code == other._type._code;
    }

    bool operator != (Error const& other) const
    {
        return _code != other._code || _type._code != other._type._code;;
    }
};

std::map<int, Error> Error::_errors;

struct Errors
{
#define BEGIN_TYPE(type, code) struct type : Error::ErrorType { type() : ErrorType(code) {} typedef Errors::##type CurrentType;
#define CAUSE(cause, code) struct cause : Error { cause() : Error(CurrentType(),code) { Error::_errors[*this] = *this; } };
#define END_TYPE() };

    // first type is coded manually to show what the macros do...
    struct Type1 : Error::ErrorType
    {
        Type1() : ErrorType(1) { }
        typedef Errors::Type1 CurrentType;

        struct Cause1 : Error
        {
            Cause1() : Error(CurrentType(),1) { Error::_errors[*this] = *this; }
        };

        struct Cause2 : Error
        {
            Cause2() : Error(CurrentType(),2) { Error::_errors[*this] = *this; }
        };
    };

    BEGIN_TYPE(Type2, 2)    
    CAUSE(Cause1, 1)
    CAUSE(Cause2, 2)
    END_TYPE()
};

以下是一些使用示例:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    Error err = Errors::Type1::Cause1();

    Q_ASSERT( err.IsOfType(Errors::Type1()) );
    Q_ASSERT( Errors::Type1::Cause1() == Errors::Type1::Cause1() );
    Q_ASSERT( Errors::Type1::Cause1() != Errors::Type2::Cause1() );

    int code = err;
    qDebug() << code;
    Q_ASSERT( Error::FromCode(code) == Errors::Type1::Cause1() );
    Q_ASSERT( Error::FromCode(code) != Errors::Type2::Cause1() );
    Q_ASSERT( Error::FromCode(code).IsOfType(Errors::Type1()) );

    return a.exec();
}

这不是一个完美的解决方案,但展示了如何以更明确的方式处理它。有许多改进可以做...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接