扩展C ++中的枚举?


Answers:


64

不,那里没有。

enum 在C ++中确实是可怜的事情,这当然是不幸的。

甚至class enumC ++ 0x中引入的方法也不能解决此可扩展性问题(尽管它们至少在类型安全方面做了一些工作)。

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

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

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

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

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


,我同意。这么多次,我一直想要一种传递标志等的类型安全的方法,但是枚举实际上只是一个整数。
Tim Allman

不幸的是,枚举并不是C ++中唯一的可怜的东西……
Johannes Overmann

9

我已经通过这种方式解决了:

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

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


是的,如果您希望将枚举定义存储在库中,那么您将无法做到这一点……
Alexis Wilke

6

如果您能够创建枚举的子类,则必须采用其他方法。

子类中的实例是超类中的实例的子集。考虑一下标准的“形状”示例。Shape类代表所有Shape的集合。Circle类及其子类表示Shapes的子集,这些Shapes是Circles。

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

(不,C ++不支持此功能。)


5
那是对继承的非常具体的解释,不一定适用于继承的所有用途。我不认为这真的可以解释为什么编译器不能支持枚举EnumEx:public Enum {D,E,F}; 这样就可以将EnumEx传递给期望枚举的函数。
jon-hanson 09年

1
@jon:因为它将破坏Liskov替换原则:D将“成为”枚举(因为它继承自Enum),但是它没有枚举的任何有效值。矛盾。枚举被设计为“枚举类型”-整个要点是,您通过定义该类型的所有有效对象来定义类型(对于C / C ++,实际上,从列出的值到所有有效类型的映射有点奇怪,并且涉及用于表示枚举的类型)。这可能是对继承的非常具体的解释,但这是一个很好的解释,并且AFAIK可以帮助您了解C ++的设计。
史蒂夫·杰索普

1
作为一个可能中断的特定示例,假设我定义了一个enum A {1, 65525};,编译器决定使用16位无符号int表示它。现在假设我定义了enumEx : public Enum { 131071 };。不能将EnumEx类型的对象作为Enum的实例传递,实际上将对其进行切片。哎呀。这就是为什么您需要C ++中的指针来执行运行时多态性的原因。我想C ++可以使每个枚举都成为最大的枚举的大小。但是从概念上讲,值131071不应是Enum的有效实例。
史蒂夫·杰索普

3
当您“扩展枚举”时,您实际上想要的是将枚举传递给需要EnumEx的函数。就像劳伦斯(Laurence)所说的,这与通常的继承是相反的,而且还不清楚继承是否是一个好的模型。枚举实际上已经很糟糕了,没有C ++使用与类继承不同的继承版本进一步混淆了事情。
史蒂夫·杰索普

@SteveJessop在所有方面都与您同在。dspeyer在此处stackoverflow.com/a/14508431/893406使用融合而不是继承得到了更好的解决方案。
v.oddou

3

解决此c ++差距的一种简单但有用的解决方法如下:

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

3

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

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

枚举类具有以下优点:

  1. 转换为已知的整数类型。
  2. 是值类型
  3. 默认情况下为constexpr,在小型处理器上不占用宝贵的RAM
  4. 由enum :: value定义范围并可以访问
  5. 适用于个案陈述
  6. 当用作参数时需要提供类型安全性,并且需要显式转换

现在,如果将一个类定义为枚举,则无法在类声明中创建该枚举的constexpr实例,因为该类尚未完成,并且会导致编译错误。同样,即使这可行,您以后也无法在另一个文件/子项目中轻松扩展枚举的值集。

现在,名称空间没有这种问题,但是它们不提供类型安全性。

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

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_;}

};

然后,对于每个要扩展和模拟的枚举类,我们创建一个名称空间和一个Type,如下所示:

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);
    }
  }
}

因此,我们有一个案例说明,枚举比较,参数类型安全性及其所有可扩展性。请注意,该集合是constexpr,不会最终在小型微型计算机上使用宝贵的RAM(在Godbolt.org上验证过的位置。:-)。作为奖励,我们可以迭代一组枚举值。



0

只是一个想法:

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

还没有完全考虑到这一点,因此欢迎对此方法发表任何评论。

一有时间,我就会尝试举一个例子说明我的意思。


0

实际上,您可以以某种方式扩展枚举。

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语句中缺少枚举值的警告时不会将附加值视为集合的一部分,因此clang和gcc将在缺少Value2时发出警告,但在上述Value4缺失时将不执行任何操作切换语句。


0

我这样做

'''
    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
    };
''''

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.