Насколько ужасный это - или действительно ли совершенно приемлемо - индексировать цикл на перечислении?
Мне определили перечисление. Значения литералов являются значениями по умолчанию. Присвоенные значения не имеют никакого значения, не будет иметь никакого значения, и значения любых литералов, добавленных в будущем, не будут также иметь никакого значения. Это просто определяется, чтобы ограничить позволенные значения и сделать вещи легче следовать. Поэтому значения будут всегда запускаться в 0 и увеличиваться на 1.
Могу я настраивать цикл как так:
enum MyEnum
{
value1,
value2,
value3,
maxValue
}
for(MyEnum i = value1; i < maxValue; i = static_cast<MyEnum>(i+1)){}
Насколько я понимаю, это просто прекрасно. Я уверен, что какой-нибудь пурист где-то там взбесится, но с точки зрения спецификации языка, этот код будет работать правильно, так что вы должны чувствовать себя свободно, если это облегчит вам жизнь.
Это может быть полезно.
enum MyEnum
{
first,
value1,
value2,
value3,
last
}
Вы можете обернуть тело цикла оператором 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
}
}
Это нормально, если вы специально не устанавливаете значение перечисляемого значения. Ваш пример хорош, но это может быть плохо:
// this would be bad
enum MyEnum
{
value1,
value2 = 2,
value3,
maxValue
};
Как указано в стандарте в 7.2 / 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++);
}
Если вам интересно, вот код. Если хотите, вы можете расширить его до итератора с произвольным доступом, предоставив +
, <
, []
и друзьям. Алгоритмы вроде 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
Я не знаю какого-либо конкретного использования этого, которое действительно полезно, и я считаю, что это плохой код. Что, если бы кто-то поместил такие вещи, как это видно во многих библиотеках?
enum MyEnum
{
value1,
value2,
value3,
maxValue = 99999;
};
Это нелегко...
Единственное решение, которое я нашел, это реализовать класс над перечислением, а затем использовать макрос для определения перечисления...
DEFINE_NEW_ENUM(MyEnum, (value1)(value2)(value3))
Основная идея состоит в том, чтобы хранить значения в контейнере STL, хранящемся статически в шаблонном классе, зависящем от символа MyEnum
. Таким образом, вы получаете совершенно четко определенную итерацию, и даже получаете недействительное состояние впустую (из-за end()
).
Я использовал отсортированный вектор
из pair
, чтобы получить преимущества красивой печати в журналах и длительной сериализации (и потому что это быстрее, чем ассоциативный контейнер для маленьких наборов данных, и занимает меньше памяти).
Я пока не нашел лучшего способа, C++11 наконец-то получит итерацию на перечислениях или нет?
Поэтому значения всегда будут начинаться с 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);
}
Я обычно определяю начальные и конечные значения в своих перечислениях, когда мне нужна итерация, со специальным синтаксисом, подобным следующему (с комментариями в стиле 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 лет кодирования в этот стиль). Это может быть нормально, поскольку я всегда помещаю все свои типы в пространство имен, чтобы избежать загрязнения и вмешательства в глобальное пространство имен.