Цикл по значениям перечисления
насколько ужасно - или вполне приемлемо-индексировать цикл на перечислении?
У меня определено перечисление. Значения литералов являются значениями по умолчанию. Присвоенные значения не имеют никакого значения, не будут иметь никакого значения, и значения любых литералов, добавленных в будущем, также не будут иметь никакого значения. Это просто определено, чтобы ограничить допустимые значения и сделать вещи проще следовать. Поэтому значения всегда будут начинаться с 0 и увеличится на 1.
могу ли я настроить цикл следующим образом:
enum MyEnum
{
value1,
value2,
value3,
maxValue
}
for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}
9 ответов
Что касается меня, то все в порядке. Я уверен, что какой-то пурист где-то там будет волноваться, но что касается спецификации языка, этот код будет работать правильно, поэтому вы должны чувствовать себя свободно, если это облегчит вашу жизнь.
Я написал итератор перечисления некоторое время назад для этих случаев
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
это нормально, если вы специально не задаете значение значения перечисления. Ваш пример прекрасен, но это может быть плохо:
// this would be bad
enum MyEnum
{
value1,
value2 = 2,
value3,
maxValue
};
как указано в стандарте в 7.2 / 1:
перечислитель-определение без инициализатора перечислитель получает значение, полученное путем увеличения значения предыдущего перечислитель по одному.
Я не знаю никакого конкретного использования этого, что на самом деле полезно, и я думаю, что это плохой код. Что делать, если положить такие вещи, как это видно во многих библиотеках ?
enum MyEnum
{
value1,
value2,
value3,
maxValue = 99999;
};
это непросто...
единственным решением, которое я когда-либо нашел, было фактически реализовать класс над перечислением, а затем использовать макрос для определения перечисления...
DEFINE_NEW_ENUM(MyEnum, (value1)(value2)(value3))
основная идея состоит в том, чтобы хранить значения в контейнере STL, хранящиеся статически в классе шаблона в зависимости от MyEnum
символ. Таким образом, вы получаете совершенно определенную итерацию, и вы даже получаете недопустимое состояние за ничто (из-за end()
).
я использовал отсортированный vector
of pair<Enum,std::string>
чтобы я мог извлечь выгоду из красивой печати в журналах и длительной сериализации (и потому, что это быстрее, чем ассоциативный контейнер для небольших наборов данных и занимает меньше памяти).
я еще не нашел лучшего способа, C++11 наконец-то получает итерацию на перечислениях или нет ?
поэтому значения всегда будут начинаться с 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-style:
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);
}
Я обычно определяю начальные и конечные значения в моих перечислениях, когда мне нужна итерация, со специальным синтаксисом, таким как следующее (с комментариями стиля 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_ помогает с пространством имен (чтобы избежать столкновений между перечислениями), а двойные подчеркивания помогают различать реальные значения и часовые итерации.
Sidenote:
написание этого я начинаю беспокоиться о двойном подчеркивании, которое не разрешено для пользователя идентификатор (это зарезервированная реализация, IIRC?), но я еще не столкнулся с проблемой (после 5-6 лет кодирования в этом стиле). Это может быть нормально, так как я всегда помещаю все свои типы в пространство имен, чтобы избежать загрязнения и вмешательства в глобальное пространство имен.
вы можете обернуть тело цикла с помощью оператора 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
}
}