【问题标题】:Loop on enumeration values循环枚举值
【发布时间】:2010-03-19 18:38:38
【问题描述】:

在枚举上索引循环有多糟糕 - 或者完全可以接受?

我定义了一个枚举。文字的值是默认值。分配的值没有任何意义,不会有任何意义,将来添加的任何文字的值也将没有任何意义。它只是被定义为限制允许的值并使事情更容易遵循。因此,这些值将始终从 0 开始并以 1 递增。

我可以这样设置循环吗:

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue
}

for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}

【问题讨论】:

  • 停下!你让我怀念帕斯卡。

标签: c++ enums loops


【解决方案1】:

前段时间我为这些情况写了一个枚举迭代器

enum Foo {
 A, B, C, Last
};

typedef litb::enum_iterator<Foo, A, Last> FooIterator;

int main() {
  FooIterator b(A), e;
  std::cout << std::distance(b, e)  << " values:" << std::endl;
  std::copy(b, e, std::ostream_iterator<Foo>(std::cout, "\n"));

  while(b != e) doIt(*b++);
}

如果你有兴趣,这里是代码。如果您愿意,您可以通过提供+&lt;[] 和朋友将其扩展为随机访问迭代器。像std::distance 这样的算法会感谢你,为当时的随机访问迭代器提供O(1) 时间复杂度。

#include <cassert>

namespace litb {

template<typename Enum, Enum Begin, Enum End>
struct enum_iterator 
  : std::iterator<std::bidirectional_iterator_tag, Enum> {
  enum_iterator():c(End) { }
  enum_iterator(Enum c):c(c) { }

  enum_iterator &operator=(Enum c) {
    this->assign(c);
    return *this;
  }

  enum_iterator &operator++() {
    this->inc();
    return *this;
  }

  enum_iterator operator++(int) {
    enum_iterator cpy(*this);
    this->inc();
    return cpy;
  }

  enum_iterator &operator--() {
    this->dec();
    return *this;
  }

  enum_iterator operator--(int) {
    enum_iterator cpy(*this);
    this->dec();
    return cpy;
  }

  Enum operator*() const {
    assert(c != End && "not dereferencable!");
    return c;
  }

  bool equals(enum_iterator other) const {
    return other.c == c;
  }

private:
  void assign(Enum c) {
    assert(c >= Begin && c <= End);
    this->c = c; 
  }

  void inc() {
    assert(c != End && "incrementing past end");
    c = static_cast<Enum>(c + 1);
  }

  void dec() {
    assert(c != Begin && "decrementing beyond begin");
    c = static_cast<Enum>(c - 1);
  }

private:
  Enum c;
};

template<typename Enum, Enum Begin, Enum End>
bool operator==(enum_iterator<Enum, Begin, End> e1, enum_iterator<Enum, Begin, End> e2) {
  return e1.equals(e2);
}

template<typename Enum, Enum Begin, Enum End>
bool operator!=(enum_iterator<Enum, Begin, End> e1, enum_iterator<Enum, Begin, End> e2) {
  return !(e1 == e2);
}

} // litb

【讨论】:

  • 这很漂亮。 :) 只需要将逗号运算符放在某个地方供您签名。
  • 为什么operator== 而不是operator&gt;(和朋友)?
  • @Chris 我当时不需要它,因为我的目标是迭代范围,所以我去实现双向迭代器接口。但是 op&gt; 对于这个枚举迭代器肯定是有意义的。
  • @Chris,实际的枚举迭代器也支持稀疏范围,因此当时支持对我的随机访问就没有意义了。我将此答案的迭代器类更改为仅支持顺序枚举。请参阅stackoverflow.com/questions/300592/enum-in-c-like-enum-in-ada/…(实际枚举迭代器和ENUM 宏的代码位于此处:johannes-schaub.de/enums.tar.gz
【解决方案2】:

就我而言,这很好。我敢肯定,某个地方的某些纯粹主义者会吓坏了,但就语言规范而言,该代码将正常工作,因此如果它能让您的生活更轻松,您应该随意使用它。

【讨论】:

    【解决方案3】:

    这样做可能会有所帮助。

    enum MyEnum
    {
        first,
        value1,
        value2,
        value3,
        last
    }
    

    【讨论】:

    • 这赋予了“第一”独特的价值。正常的循环习语是半开的——开头包含,结尾不包含。我建议在枚举末尾使用“first = value1”,或者简单地重命名“value1”,使其清楚地表明它是并且永远是第一个。
    【解决方案4】:

    只要您不专门设置枚举值的值就可以了。您的示例很好,但这可能很糟糕:

    // this would be bad
    enum MyEnum
    {
        value1,
        value2 = 2,
        value3,
        maxValue
    };
    

    按照 7.2/1 中的标准规定:

    枚举器定义 没有初始化器给枚举器通过增加前一个值获得的值 枚举数加一。

    【讨论】:

    • 这就是为什么我指定文字的值不重要并且永远不会重要。他们得到默认值。
    • @Rachel - 我只是想弄清楚可能出了什么问题。正如我在回答的第一句话中所说的那样,您这样做的方式非常好。
    【解决方案5】:

    我不知道这个实际上有用的任何具体用途,我认为这是糟糕的代码。 如果像在许多图书馆中看到的那样放置这样的东西会怎样?

    enum MyEnum
    {
        value1,
        value2,
        value3,
        maxValue = 99999;
    };
    

    【讨论】:

    • IMO,这是向枚举添加评论的好理由 - 告诉他们不要这样做。
    【解决方案6】:

    这并不容易......

    我找到的唯一解决方案是在枚举上实际实现一个类,然后使用宏来定义枚举...

    DEFINE_NEW_ENUM(MyEnum, (value1)(value2)(value3))
    

    基本思想是将值存储在 STL 容器中,根据MyEnum 符号静态存储在模板类中。通过这种方式,您可以获得一个完美定义的迭代,甚至会获得一个无效的状态(因为end())。

    我使用了排序后的vectorpair&lt;Enum,std::string&gt;,这样我就可以从漂亮的日志打印和持久的序列化中受益(而且因为它比用于少量数据集的关联容器更快并且占用更少的内存)。

    我还没有找到更好的方法,C++11 是否最终会在枚举上进行迭代?

    【讨论】:

      【解决方案7】:

      因此值将始终开始 为 0 并增加 1。

      请记住,除非这已写入您工作的编码标准,否则当维护程序员在 6 个月内出现并对枚举进行更改时:

      enum MyEnum 
      { 
          value1, 
          value2 = 5, 
          value3, 
          maxValue 
      }
      

      由于一项新要求,您将解释为什么他们的合法更改会破坏应用程序。

      要将值附加到名称,您可以使用地图:

      typedef std::map<std::string,int> ValueMap;
      ValueMap myMap;
      myMap.insert(make_pair("value1", 0));
      myMap.insert(make_pair("value2", 1));
      myMap.insert(make_pair("value3", 2));
      
      for( ValueMap::iterator iter = theMap.begin(); iter != theMap.end(); ++iter )
      {
        func(iter->second);
      }
      

      如果名称无关紧要,您可以使用 C 样式的数组:

      int values[] = { 0, 1, 2 };
      int valuesSize = sizeof(values) / sizeof(values[0]);
      
      for (int i = 0; i < valuesSize; ++i)
      {
        func(values[i]);
      }
      

      或使用 std::vector 的类似方法。

      另外,如果你说的是真的,the values will always start at 0 and increase by 1 那么我很困惑为什么这不起作用:

      const int categoryStartValue = 0;
      const int categoryEndValue = 4;
      
      for (int i = categoryStartValue; i < categoryEndValue; ++i)
      {
        func(i);
      }
      

      【讨论】:

        【解决方案8】:

        当我需要迭代时,我通常在我的枚举中定义开始和结束值,使用如下特殊语法(使用 doxygen 样式的 cmets 来澄清):

        enum FooType {
            FT__BEGIN = 0,        ///< iteration sentinel
            FT_BAR = FT__BEGIN,   ///< yarr!
            FT_BAZ,               ///< later, when the buzz hits  
            FT_BEEP,              ///< makes things go beep
            FT_BOOP,              ///< makes things go boop
            FT__END              ///< iteration sentinel
        }
        

        FT_ 有助于命名空间(以避免枚举之间的冲突),双下划线有助于区分实际值和迭代标记。

        旁注:

        写这篇文章时,我开始担心用户标识符不允许使用双下划线(这是保留的实现,IIRC?),但我还没有遇到问题(经过 5-6 年的这种风格的编码)。这可能没问题,因为我总是将所有类型都放在一个命名空间中以避免污染和干扰全局命名空间。

        【讨论】:

          【解决方案9】:

          您可以使用 switch 语句包装循环体,以防止出现非增量值。它可能超级慢,如 maxValue = 9999;情况,但这可能不是最糟糕的事情。我一直在我们的代码库中看到这种风格

          enum MyEnum  {
           value1,  
           value2,  
           value3,  
           maxValue  
          }  
          for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){
               switch(i)
               {
                 case value1:
                 case value2:
                 case value3:
                              //actual code
               }
          
          }  
          
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-01-30
          • 1970-01-01
          • 1970-01-01
          • 2013-06-12
          • 2019-06-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多