Цикл на перечислимых величинах

Насколько ужасный это - или действительно ли совершенно приемлемо - индексировать цикл на перечислении?

Мне определили перечисление. Значения литералов являются значениями по умолчанию. Присвоенные значения не имеют никакого значения, не будет иметь никакого значения, и значения любых литералов, добавленных в будущем, не будут также иметь никакого значения. Это просто определяется, чтобы ограничить позволенные значения и сделать вещи легче следовать. Поэтому значения будут всегда запускаться в 0 и увеличиваться на 1.

Могу я настраивать цикл как так:

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue
}

for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}
9
задан Rachel 19 March 2010 в 18:38
поделиться

9 ответов

Насколько я понимаю, это просто прекрасно. Я уверен, что какой-нибудь пурист где-то там взбесится, но с точки зрения спецификации языка, этот код будет работать правильно, так что вы должны чувствовать себя свободно, если это облегчит вам жизнь.

4
ответ дан 4 December 2019 в 14:28
поделиться

Это может быть полезно.

enum MyEnum
{
    first,
    value1,
    value2,
    value3,
    last
}
2
ответ дан 4 December 2019 в 14:28
поделиться

Вы можете обернуть тело цикла оператором 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
     }

}  
-1
ответ дан 4 December 2019 в 14:28
поделиться

Это нормально, если вы специально не устанавливаете значение перечисляемого значения. Ваш пример хорош, но это может быть плохо:

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

Как указано в стандарте в 7.2 / 1:

Определение перечислителя без инициализатора дает перечислителю значение, полученное путем увеличения значения предыдущий перечислитель по одному.

0
ответ дан 4 December 2019 в 14:28
поделиться

Некоторое время назад я написал итератор перечисления для этих случаев

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

Если вам интересно, вот код. Если хотите, вы можете расширить его до итератора с произвольным доступом, предоставив + , <, [] и друзьям. Алгоритмы вроде 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
9
ответ дан 4 December 2019 в 14:28
поделиться

Я не знаю какого-либо конкретного использования этого, которое действительно полезно, и я считаю, что это плохой код. Что, если бы кто-то поместил такие вещи, как это видно во многих библиотеках?

enum MyEnum
{
    value1,
    value2,
    value3,
    maxValue = 99999;
};
0
ответ дан 4 December 2019 в 14:28
поделиться

Это нелегко...

Единственное решение, которое я нашел, это реализовать класс над перечислением, а затем использовать макрос для определения перечисления...

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

Основная идея состоит в том, чтобы хранить значения в контейнере STL, хранящемся статически в шаблонном классе, зависящем от символа MyEnum. Таким образом, вы получаете совершенно четко определенную итерацию, и даже получаете недействительное состояние впустую (из-за end()).

Я использовал отсортированный вектор из pair, чтобы получить преимущества красивой печати в журналах и длительной сериализации (и потому что это быстрее, чем ассоциативный контейнер для маленьких наборов данных, и занимает меньше памяти).

Я пока не нашел лучшего способа, C++11 наконец-то получит итерацию на перечислениях или нет?

0
ответ дан 4 December 2019 в 14:28
поделиться

Поэтому значения всегда будут начинаться с 0 и увеличиваться на 1.

Имейте в виду, что если это не записано в стандарте кодирования, где вы работаете, когда через 6 месяцев придет программист по обслуживанию и внесет изменения в перечисление типа:

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

из-за нового требования, вам придется объяснять, почему их законное изменение сломало приложение.

Чтобы прикрепить значения к именам, вы можете использовать map:

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.

Также, если то, что вы говорите, правда, и значения всегда будут начинаться с 0 и увеличиваться на 1, тогда я не понимаю, почему это не будет работать:

const int categoryStartValue = 0;
const int categoryEndValue = 4;

for (int i = categoryStartValue; i < categoryEndValue; ++i)
{
  func(i);
}
0
ответ дан 4 December 2019 в 14:28
поделиться

Я обычно определяю начальные и конечные значения в своих перечислениях, когда мне нужна итерация, со специальным синтаксисом, подобным следующему (с комментариями в стиле doxygen для пояснения):

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 лет кодирования в этот стиль). Это может быть нормально, поскольку я всегда помещаю все свои типы в пространство имен, чтобы избежать загрязнения и вмешательства в глобальное пространство имен.

0
ответ дан 4 December 2019 в 14:28
поделиться
Другие вопросы по тегам:

Похожие вопросы: