【问题标题】:Why can't I increment a variable of an enumerated type?为什么我不能增加枚举类型的变量?
【发布时间】:2010-08-13 08:29:27
【问题描述】:

我有一个枚举类型StackID,我使用枚举来引用特定向量的索引,它使我的代码更易于阅读。

但是,我现在需要创建一个名为nextAvail 的变量,类型为StackID。 (它实际上是指一个特定的 stackID )。我试图增加它,但在 C++ 中,以下是非法的:

nextAvail++;

这对我来说是有意义的......因为没有边界检查。

我可能忽略了一些明显的东西,但是什么是好的替代品?


我也想链接到this问题。

【问题讨论】:

    标签: c++


    【解决方案1】:

    我可能忽略了一些明显的东西,但有什么好的替代品?

    重载operator++:

    // Beware, brain-compiled code ahead! 
    StackID& operator++(StackID& stackID)
    {
    #if MY_ENUMS_ARE_CONTIGUOUS && I_DO_NOT_WORRY_ABOUT_OVERFLOW
      return stackID = static_cast<StackID>( ++static_cast<int>(stackID) );
    #else
      switch(stackID) {
        case value1 : return stackID = value2;
        case value2 : return stackID = value3;
        ...
        case valueN : return stackID = value1;
      }
      assert(false);
      return stackID; // some compilers might warn otherwise
    #endif
    }
    
    StackID operator++(StackID& stackID, int)
    {
      StackID tmp(stackID);
      ++stackID;
      return tmp;
    }
    

    【讨论】:

    • @sbi - 我这样做肯定会让事情变得简单。但是对于那些建议enum 旨在表达一组任意值的发帖人,你有什么看法?
    • @BeeBand - 如果“++”有任何用途,我认为您有充分的理由超越传统的 C++ 使用枚举作为编译时检查的整数。另一方面,Sbi 的代码可以做一些边界检查。在 Java 中,枚举只是一种具有不可修改成员的轻量级类——在其中放置方法是公认的做法。见download.oracle.com/javase/1.5.0/docs/guide/language/enums.html。
    • @BeeBand:我不同意。很可能十分之五的教科书enum 示例类似于enum month { Jan, Feb, ..., Dec };,因此是可以递增的枚举的完美示例。在过去 15 年的 C++ 工作中,我重载了 operator++ 的枚举可能不到五次。但我已经做到了。
    • @GMan & @Martin: &lt;sigh&gt; 你能说我正在度假,坐在花园里,每天两次把笔记本电脑放在膝盖上,我懒得启动编译器吗? @BeeBand:抱歉搞砸了。值得庆幸的是,社区抓住了这些。
    • @Streppel:谢谢! (当然,由于我用警告限定了我的代码,我不会因为缺少大括号而感到难过...:´^&gt;)
    【解决方案2】:

    因为枚举不必是连续的。例如。举个例子:

    enum Colors {
     cRed, // = 0
     cBlue, // = 1
     cGreen = 3
    }
    

    在这种情况下会发生什么?

    Colors color = cBlue;
    Colors other = color++;
    

    应该是 cGreen 还是应该是 2。在这种情况下,它不再是有效的枚举成员。这个呢?

    Colors color = cGreen;
    Colors other = color++;
    

    other 应该是 cRed(环绕)还是 4?

    如您所见,能够增加枚举值会带来很多问题,并使它们原本打算成为的简单机制变得复杂。

    如果您只关心递增的整数值,则只需转换为 int 并递增它。

    【讨论】:

    • @Igor:现在将您的示例替换为:enum month ...。月份是连续的并且自然递增。然后呢?
    • @sbi:哦,几个月,这改变了一切,因为几个月很简单。 . .等等,当你增加 12 月时会发生什么?它应该抛出溢出异常,还是应该环绕?您可能很清楚会发生什么,但我可能需要不同的功能。关于这应该如何工作的问题太多了,语言设计者不允许这样做是正确的,并让每个案例都单独处理。
    • @Igor:谢谢!我认为您的回答非常有用,但是我接受了 sbi 的回答。我想我在这里有点作弊,因为我在帖子中基本上问了两个问题。一个在标题中,我认为你在你的答案中指出了这一点,另一个是我帖子的最后一行,我认为要点是 sbi。
    • @BeeBand 呵呵,没关系。很高兴我回答了您的一些问题。
    • @Binary:我不确定你的日历,但在这附近,十二月之后到一月。这与增加std::numeric_limits&lt;int&gt;::max() 时几乎相同。无论如何,问题不在于为什么会这样(“哪种对我来说有意义......”),而是要做什么(“什么是好的替代品?”)。
    【解决方案3】:

    在int 之间来回转换当然是显而易见的解决方案,然后您明确表示您了解添加发生在enum“外部”:

    nextAvail = static_cast<StackID>(static_cast<int>(nextAvail) + 1);
    

    【讨论】:

    • 嗯。是的,那是我的另一个选择,我只是觉得所有这些演员让代码看起来有点乱。
    【解决方案4】:

    如果您要对其进行算术运算,为什么不将nextAvail 存储为int?

    另一种选择是将枚举包装在您自己的类型中并为其重载operator ++(例如也可以环绕或其他东西)。

    【讨论】:

    • 我认为这就是我所追求的 - 将枚举包装在我自己的类型中,然后我可以做我喜欢的事情。你能指出一个例子的方向吗?
    【解决方案5】:

    枚举在语义上应该代表一组不同的相关值。

    所以你可以拥有

    enum Colour {RED, GREEN, BLUE};
    

    但这应该相当于:

    enum Colour {GREEN, BLUE, RED};
    

    问题是,如果你增加一个枚举,那么这些表示就不一样了。第一种情况下的 GREEN++ 与第二种情况下的 GREEN++ 不同。

    让你的程序依赖于枚举的声明是灾难的根源——维护者可能认为枚举的顺序无关紧要,从而引入了许多无声的错误。

    【讨论】:

    • 但可以明确地将值分配给枚举,例如enum Colour {RED = 0, GREEN = 1, BLUE = 2}。在这种情况下,顺序无关紧要。
    • 是的,但是你已经超越了枚举应该是什么,而是作为一组键/值对进入它。
    • 不清楚你说“应该是等价的”是在暗示什么,你能详细说明一下吗?
    • @BeeBand:更改枚举中的顺序不应该改变代码的行为;但在你的情况下,它会。
    • 当然。第一个声明表达了红、绿、蓝三种颜色的事实。这就是它所说的。但第二个说的也一样。代码的维护者可以完全合理地在绿色和蓝色之间添加一种颜色,黄色。但是任何假设(隐式或显式)GREEN + 1 = BLUE 的代码都会默默地中断。简而言之,枚举用于表达具有共同属性(在这种情况下它们是颜色)的值集,而不是它们之间的关系(绿色在蓝色之前,或者在红色之后......)
    【解决方案6】:

    非常简单:

    nextAvail = (StackID)(nextAvail + 1);

    【讨论】:

    【解决方案7】:

    枚举类型将是int,因此您可以强制转换它们。这是你想要做的吗?

    int ndx = (int) StackID.SomeValue;
    ...
    ++ndx;
    

    当然,这会让一些人非常困惑。

    我突然想到你使用的是enum,而你应该使用const,甚至是#define。 enum 最适合您有 任意 值(其中确切的值没有意义)。

    【讨论】:

    • 我认为你可能是对的。常量。我的代码假定确切的值是有意义的,并且正如您(和其他海报)所建议的那样,破坏了枚举的预期含义。
    • 枚举并不总是int类型。
    【解决方案8】:

    我以这种方式重载了++/-- 运算符:

    enum STATE {STATE_1, STATE_2, STATE_3, STATE_4, STATE_5, STATE_6};
    

    //重载STATE++操作符

    inline STATE& operator++(STATE& state, int) {
        const int i = static_cast<int>(state)+1;
        state = static_cast<STATE>((i) % 6);
        return state;
    }
    

    //重载STATE--操作符

    inline STATE& operator--(STATE& type, int) {
        const int i = static_cast<int>(type)-1;
    
        if (i < 0) {
            type = static_cast<STATE>(6);
        } else {
            type = static_cast<STATE>((i) % 6);
        }
        return type;
    }
    

    【讨论】:

      【解决方案9】:

      关于 oprator++,$5.2.6/1 声明-“操作数的类型应为算术类型或指向完整对象类型的指针。”

      StackID 在这里不合适。为枚举类型。

      一个选项是这样的

      $5.7/1 - “对于加法,两个操作数都应具有算术或枚举类型,或者一个操作数应是指向完全定义的对象类型的指针,而另一个应具有整数或枚举类型。”

      enum Possibility {Yes, No, Maybe};
      
      Possibility operator++(Possibility const& r){
         return Possibility(r + 1);   // convert r to integer, add 1, convert back to Enum
      }
      
      int main(){
         Possibility p = Yes;
         Possibility p1 = ++p;
      }
      

      【讨论】:

      • 很明显(对发帖人来说也是如此)这是非法的,问题是什么是好的替代品
      • 如果你重载prefix增量,你也应该总是重载postfix增量。
      【解决方案10】:

      我对这个 C 加 C++ 解决方案非常满意,因为 for 循环增加了一个枚举。

      for (Dwg_Version_Type v = R_INVALID; v <= R_AFTER; v++)
      

      =>

      int vi;
      for (Dwg_Version_Type v = R_INVALID; 
           v <= R_AFTER; 
           vi = (int)v, vi++, v = (Dwg_Version_Type)vi)
      

      这里的其他解决方案不是C backcompat,而且相当大。

      【讨论】:

        猜你喜欢
        • 2013-11-19
        • 2010-10-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-12-06
        相关资源
        最近更新 更多