【问题标题】:Extending enums in C++?在 C++ 中扩展枚举?
【发布时间】:2010-12-20 18:18:12
【问题描述】:

C++ 中有没有办法扩展/“继承”枚举?

IE:

enum Enum {A,B,C};
enum EnumEx : public Enum {D,E,F};

或者至少定义它们之间的转换?

【问题讨论】:

标签: c++ enums


【解决方案1】:

http://www.codeproject.com/KB/cpp/InheritEnum.aspx 遍历一个创建扩展枚举的方法。

创建 InheritEnum.h:

// -- InheritEnum.h

template <typename EnumT, typename BaseEnumT>
class InheritEnum
{
public:
  InheritEnum() {}
  InheritEnum(EnumT e)
    : enum_(e)
  {}

  InheritEnum(BaseEnumT e)
    : baseEnum_(e)
  {}

  explicit InheritEnum( int val )
    : enum_(static_cast<EnumT>(val))
  {}

  operator EnumT() const { return enum_; }
private:
  // Note - the value is declared as a union mainly for as a debugging aid. If 
  // the union is undesired and you have other methods of debugging, change it
  // to either of EnumT and do a cast for the constructor that accepts BaseEnumT.
  union
  { 
    EnumT enum_;
    BaseEnumT baseEnum_;
  };
};

然后使用:

enum Fruit { Orange, Mango, Banana };
enum NewFruits { Apple, Pear }; 
typedef InheritEnum< NewFruit, Fruit > MyFruit;

void consume(MyFruit myfruit); 

YMMV.

【讨论】:

  • 一种非常危险的方法。如果两个枚举放在一起使用相同的值,这会默默地混淆它们。
  • @dspeyer 它仍然为整个辩论带来了一个有趣的想法。你的解决方案就是这个的后代。
  • 答案不应该只是链接。 (并且该代码在其“强制转换”中具有未定义的行为。)
  • 我按照那里的说明进行操作,现在我想从一个基础枚举扩展多个枚举。试图在这里问我的问题如何使用参数包:stackoverflow.com/questions/69063662/…
【解决方案2】:

我这样做

enum OPC_t // frame Operation Codes
{
      OPC_CVSND = 0 // Send CV value
    , OPC_CVREQ = 1 // Request CV (only valid for master app)
    , OPC_COMND = 2 // Command
    , OPC_HRTBT = 3 // Heart Beat
};
enum rxStatus_t     // this extends OPC_t
{
      RX_CVSND = OPC_CVSND  // Send CV value
    , RX_CVREQ = OPC_CVREQ  // Request CV
    , RX_COMND = OPC_COMND  // Command
    , RX_HRTBT = OPC_HRTBT  // Heart Beat
    , RX_NONE       // No new Rx
    , RX_NEWCHIP        // new chip detected
};

【讨论】:

    【解决方案3】:

    大家可以试试这个:)

    
    struct EnumType
    {
            enum 
            {
                None = 0,
                Value_1 = 1,
                Value_2 = 2,
                Value_3 = 3
            };
    
            //For when using the EnumType as a variable type
            int Value { None };
    };
    
    struct EnumTypeEx : EnumType
    {
            enum
            {
                ExValue_1 = 3,
                ExValue_2 = 4,
                
                //override the value of Value_3
                Value_3 = 3000
            };
    };
    

    优点:

    • 类可扩展性
    • 可以覆盖基本标签值

    缺点:

    • 写起来乏味
    • 您必须明确设置每个标签的值(*)

    (*) 您可以从最后一个基值开始自动增加值,只需明确地编写它,例如。 ExValue_Start = LastBaseValue。

    【讨论】:

      【解决方案4】:

      我是这样解决的:

      typedef enum
      {
          #include "NetProtocols.def"
      } eNetProtocols, eNP;
      

      当然,如果在 NetProtocols.def 文件中添加新的 net 协议,则必须重新编译,但至少可以扩展。

      “NetProtocols.def”将仅包含字段名称:

      HTTP,
      HTTPS,
      FTP
      

      【讨论】:

      • 是的,如果您希望枚举定义在库中,您将无法这样做...
      • @macro 这对我不起作用。您能否列出 NetProtocols.def 的一些内容
      【解决方案5】:

      我在一些在我设计的小型硬件设备上运行的项目中遇到了这个问题。有一个包含许多服务的通用项目。其中一些服务使用枚举作为参数来获得额外的类型检查和安全性。我需要能够在使用这些服务的项目中扩展这些枚举。

      正如其他人所提到的,c++ 不允许您扩展枚举。但是,您可以使用具有 enum 类 的所有优点的命名空间和模板来模拟枚举。

      枚举类有以下好处:

      1. 转换为已知的整数类型。
      2. 是值类型
      3. 默认为 constexpr,在小型处理器上不占用宝贵的 RAM
      4. 由 enum::value 限定和访问
      5. 适用于案例陈述
      6. 在用作参数并需要显式强制转换时提供类型安全

      现在,如果您将一个类定义为 enum,则不能在类声明中创建 enumconstexpr 实例,因为类还没有完成,它会导致编译错误。此外,即使这有效,您也无法稍后在另一个文件/子项目中轻松扩展枚举值集。

      现在命名空间没有这样的问题,但它们不提供类型安全。

      答案是首先创建一个模板化的基类,它允许不同基数大小的枚举,这样我们就不会浪费我们不使用的东西。

      template <typename TYPE>
      class EnumClass {
        private:
          TYPE value_;
        public:
          explicit constexpr EnumClass(TYPE value) :
              value_(value){
          }
          constexpr EnumClass() = default;
          ~EnumClass() = default;
          constexpr explicit EnumClass(const EnumClass &) = default;
          constexpr EnumClass &operator=(const EnumClass &) = default;
      
          constexpr operator TYPE() const {return    value_;}
          constexpr TYPE value() const {return value_;}
      
      };
      

      然后对于我们想要扩展和模拟的每个 枚举类,我们创建一个命名空间和一个像这样的类型:

      namespace EnumName {
         class Type :public Enum<uint8_t> {
           public:
              explicit constexpr Type(uint8_t value): Enum<uint8_t>(value){}
              constexpr Enum() = default;
         }
         constexpr auto Value1 = Type(1); 
         constexpr auto Value2 = Type(2); 
         constexpr auto Value3 = Type(3); 
      }
      

      如果你已经包含了原始的 EnumName,那么稍后在你的代码中你可以这样做:

         namespace EnumName {
             constexpr auto Value4 = Type(4U); 
             constexpr auto Value5 = Type(5U); 
             constexpr auto Value6 = Type(6U); 
      
             constexpr std::array<Type, 6U> Set = {Value1, Value2, Value3, Value4, Value5, Value6};
          }
      

      现在你可以像这样使用枚举了:

      #include <iostream>
      
      void fn(EnumName::Type val){
          if( val != EnumName::Value1 ){
            std::cout << val;
          }
      }
      
      int main(){
        for( auto e :EnumName::Set){
          switch(e){
            case EnumName::Value1:  
              std::cout << "a";
              break;
            case EnumName::Value4:  
              std::cout << "b";
              break;
            default:
              fn(e);
          }
        }
      }
      

      所以我们有一个 case 语句、枚举比较、参数类型安全和所有可扩展的。请注意,该集合是 constexpr 并且最终不会在小型微型计算机上使用宝贵的 RAM(在 Godbolt.org 上验证的位置。:-)。作为奖励,我们能够迭代一组枚举值。

      【讨论】:

        【解决方案6】:

        实际上你可以通过一种方式来扩展枚举。

        C++ 标准将有效枚举值定义为基础类型的所有有效值,因此以下是有效的 C++ (11+)。它不是未定义的行为,但它非常讨厌 - 你已被警告过。

        #include <cstdint>
        
            enum Test1:unit8_t {
                Value1 =0,
                Value2 =1
            };
        
            constexpr auto Value3 = static_cast<Test1>(3);
            constexpr auto Value4 = static_cast<Test1>(4);
            constexpr auto Value5 = static_cast<Test1>(5);
        
        
        Test1 fn(Test1 val){
          switch(val){
            case Value1:
            case Value2:
            case Value3:
            case Value4:
               return Value1;
            case Value5:
               return Value5; 
          } 
        }
        
        int main(){
          return static_cast<uint8_t>(fn(Value5));
        } 
        

        请注意,大多数编译器不会将附加值视为生成有关 switch 语句中缺少枚举值的警告的集合的一部分。所以 如果缺少 Value2,clang 和 gcc 将发出警告,但如果在上述 switch 语句中缺少 Value4,则不会执行任何操作。

        【讨论】:

          【解决方案7】:

          对于这种 c++ 差距,一个简单但有用的解决方法如下:

          #define ENUM_BASE_VALS A,B,C
          enum Enum {ENUM_BASE_VALS};
          enum EnumEx {ENUM_BASE_VALS, D,E,F};
          

          【讨论】:

            【解决方案8】:

            只是一个想法:

            您可以尝试为每个常量创建一个空类(也许将它们全部放在同一个文件中以减少混乱),为每个类创建一个实例并将指向这些实例的指针用作“常量”。这样,编译器将理解继承,并在使用函数调用时执行任何必要的 ChildPointer-to-ParentPointer 转换,并且编译器仍然会进行类型安全检查,以确保没有人将无效的 int 值传递给函数(这将有如果您使用 LAST 值方法“扩展”枚举,则使用该方法)。

            虽然还没有完全考虑到这一点,所以欢迎任何采用这种方法的 cmets。

            一旦有时间,我会尝试发布一个示例来说明我的意思。

            【讨论】:

              【解决方案9】:

              如果您能够创建枚举的子类,它就必须反过来工作。

              子类中的实例集是超类中实例的子集。想想标准的“形状”示例。 Shape 类表示所有形状的集合。 Circle 类,它的子类,代表圆形的子集。

              为了保持一致,枚举的子类必须包含它所继承的枚举中元素的子集。

              (不,C++ 不支持这个。)

              【讨论】:

              • 这是对继承的一种非常具体的解释,不一定适用于继承的所有用途。我认为这并不能真正解释为什么编译器不支持 enum EnumEx : public Enum {D,E,F};这样可以将 EnumEx 传递给期望 Enum 的函数。
              • @jon:因为它会破坏 Liskov 替换原则:D 将“成为”枚举(因为它继承自它),但它不会具有枚举的任何有效值。矛盾。枚举被设计为“枚举类型” - 重点是您通过定义该类型的所有有效对象来定义类型(在 C/C++ 的情况下,实际上从列出的值到所有有效类型的映射有点奇怪,并涉及用于表示枚举的类型)。这可能是对继承的一种非常具体的解释,但它是一个很好的解释,并且 AFAIK 它为 C++ 的设计提供了信息。
              • 作为一个会中断的特定示例,假设我定义了一个 enum A {1, 65525}; 并且编译器决定使用 16 位无符号整数来表示它。现在假设我定义enumEx : public Enum { 131071 };。这个 EnumEx 类型的对象不可能作为 Enum 的实例传递,它实际上会被切片。哎呀。这就是为什么您需要 C++ 中的指针来执行运行时多态性。我猜 C++ 可以使每个枚举都成为可能的最大枚举的大小。但从概念上讲,值 131071 不应该是 Enum 的有效实例。
              • 当你“扩展一个枚举”时,你真正想要的可能是将一个 Enum 传递给一个期望 EnumEx 的函数。正如 Laurence 所说,这是与通常的继承相反的方式,而且完全不清楚继承是否是一个很好的模型。枚举本身已经够糟糕了,没有 C++ 使用不同于类继承的继承版本进一步混淆事情。
              • 这不是解释。在 C++ 中,完全可以说例如struct S{ enum {A,B,C}; }; struct T : S {enum {D=3,E,F};};,所以现在T,作为S 的子类,仍然包含元素的超集ABCDEF
              【解决方案10】:

              不,没有。

              enum 在 C++ 中确实是个可怜的东西,这当然是不幸的。

              即使是 C++0x 中引入的 class enum 也没有解决这个可扩展性问题(尽管它们至少为类型安全做了一些事情)。

              enum 的唯一优点是它们不存在:它们提供了一些类型安全性,同时由于它们被编译器直接替换,因此不会产生任何运行时开销。

              如果你想要这样的野兽,你必须自己动手:

              • 创建一个类MyEnum,其中包含一个int(基本上)
              • 为每个有趣的值创建命名构造函数

              您现在可以随意扩展您的类(添加命名构造函数)...

              虽然这是一种解决方法,但我从未找到处理枚举的令人满意的方法......

              【讨论】:

              • 唉,我同意。很多时候我都想要一种类型安全的方式来传递标志等,但枚举实际上只是整数。
              • 不幸的是,枚举并不是 C++ 中唯一糟糕的东西......
              【解决方案11】:

              以下代码运行良好。

              enum Enum {A,B,C};
              enum EnumEx {D=C+1,E,F};
              

              【讨论】:

              • 真的没用。 A 仍然是 Enum 而不是 EnumEx。 IE。枚举 x = A;没有强制转换将无法编译。
              • 实际上,我更关心的是对类型系统的影响,而不是值的索引。
              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-07-09
              • 1970-01-01
              • 1970-01-01
              • 2011-09-24
              相关资源
              最近更新 更多