2010-03-19 8 views
9

Quelle est l'horreur - ou est-ce parfaitement acceptable - d'indexer une boucle sur une énumération?Boucle sur les valeurs d'énumération

J'ai une énumération définie. Les valeurs des littéraux sont des valeurs par défaut. Les valeurs assignées n'ont aucune signification, n'auront aucune signification, et les valeurs des littéraux ajoutés dans le futur n'auront aucune signification. C'est juste défini pour limiter les valeurs permises et pour rendre les choses plus faciles à suivre. Par conséquent, les valeurs commencent toujours à 0 et augmenter de 1.

Puis-je mettre en place une boucle comme ceci:

enum MyEnum 
{ 
    value1, 
    value2, 
    value3, 
    maxValue 
} 

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

Stop! Tu me rends nostalgique de Pascal. –

Répondre

4

En ce qui me concerne, c'est très bien. Je suis sûr que certains puristes quelque part vont paniquer, mais en ce qui concerne les spécifications linguistiques, ce code fonctionnera correctement, donc vous devriez vous sentir libre d'y aller si cela vous facilite la vie.

0

C'est très bien tant que vous ne définissez pas spécifiquement la valeur d'une valeur enum. Votre exemple est très bien, mais cela pourrait être mauvais:

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

Comme les états standards de 7.2/1:

Énumérateur définition sans initialiseur donne le recenseur la valeur obtenue en augmentant la valeur de l'énumérateur précédent par un.

+2

C'est pourquoi j'ai spécifié que les valeurs des littéraux ne sont pas significatives et ne le seront jamais. Ils obtiennent les valeurs par défaut. – Rachel

+1

@Rachel - J'essayais juste d'être clair sur ce qui pourrait mal se passer. Comme je l'ai dit dans la première phrase de ma réponse, c'est parfaitement bien comme ça. –

0

Je ne connais pas d'utilisation spécifique de ce qui est réellement utile et je pense que c'est un mauvais code. Et si on mettait des choses comme ça dans de nombreuses bibliothèques?

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

Ensuite, ils auraient malicieusement brisé mon code. – Rachel

+0

IMO, c'est une bonne raison d'ajouter un commentaire à l'énumération - dites-leur de ne pas le faire. – Steve314

2

Cela peut être utile de le faire.

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

Cela donne "premier" sa propre valeur unique. Les idiomes de boucle normaux sont semi-ouverts - inclusifs au début et exclusifs à la fin. Je suggère d'utiliser "first = value1" à la fin de l'enum, ou simplement de renommer "value1" d'une manière qui le rend clair et qui sera toujours le premier. – Steve314

0

Par conséquent, les valeurs commencent toujours à 0 et augmenter de 1.

Gardez à l'esprit que si cela est écrit dans la norme de codage où vous travaillez, lorsqu'un programmeur de maintenance vient le long de 6 mois et fait un changement à une énumération comme:

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

en raison d'une nouvelle exigence, vous aurez à expliquer pourquoi leur changement légitime a brisé l'application.

Pour attribuer des valeurs aux noms que vous pouvez utiliser une carte:

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

Si les noms ne sont pas importants, vous pouvez utiliser un tableau de style C:

int values[] = { 0, 1, 2 }; 
int valuesSize = sizeof(values)/sizeof(values[0]); 

for (int i = 0; i < valuesSize; ++i) 
{ 
    func(values[i]); 
} 

ou d'une méthode comparable à l'aide un std :: vector.

De plus, si ce que vous dites est vrai, et the values will always start at 0 and increase by 1 alors je suis confus pour expliquer pourquoi cela ne fonctionnerait pas:

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

for (int i = categoryStartValue; i < categoryEndValue; ++i) 
{ 
    func(i); 
} 
-1

Vous pouvez envelopper le corps de la boucle avec une instruction switch pour protéger contre les non -valeurs incrémentales. C'est potentiellement super lent, comme dans le maxValue = 9999; mais ce n'est peut-être pas la pire des choses. Je vois ce style ici tout le temps dans nos bases de code

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 
    } 

} 
+0

Ahhh! Le motif For/Case redouté! http://thedailywtf.com/Articles/The_FOR-CASE_paradigm.aspx http://thedailywtf.com/Articles/Switched_on_Loops.aspx –

0

Pas facile ...

La seule solution que j'ai jamais trouvée était de mettre en œuvre en fait une classe sur l'ENUM, puis utiliser une macro pour définir la ENUM ...

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

l'idée de base est de stocker les valeurs dans un conteneur STL, stocké de manière statique dans une classe de modèle selon le symbole MyEnum. De cette façon, vous obtenez une itération parfaitement bien définie, et vous obtenez même un état invalide pour rien (à cause du end()). J'ai utilisé un vector trié de pair<Enum,std::string> afin que je puisse bénéficier de jolies impressions dans les journaux et de sérialisation longue durée (et parce qu'il est plus rapide qu'un conteneur associatif pour les petits ensembles de données et prend moins de mémoire).

Je n'ai pas encore trouvé de meilleur moyen, est-ce que C++ 11 obtient finalement l'itération sur enums ou pas?

8

j'ai écrit un iterator ENUM il y a un moment pour ces cas

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

Si vous êtes intéressé, est le code ici. Si vous le souhaitez, vous pouvez l'étendre pour devenir un itérateur à accès aléatoire en fournissant +, <, [] et vos amis. Des algorithmes tels que std::distance vous remercient en fournissant O(1) complexité temporelle pour l'itérateur à accès aléatoire de l'époque.

#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 
+0

C'est lisse. :) Je dois juste jeter l'opérateur de virgule quelque part pour votre signature. – GManNickG

+0

Pourquoi 'operator ==' mais pas 'operator>' (et amis)? –

+0

@Chris je n'en avais pas besoin à l'époque puisque mon but était l'itération de la gamme, j'ai donc mis en place l'interface bidirectionnelle-itérateur. Mais un 'op>' aurait certainement du sens pour cet itérateur enum. –

0

Je définissent habituellement commencer et les valeurs de fin dans mes énumérations quand j'ai besoin itération, avec une syntaxe spéciale comme celle-ci (avec des commentaires de style doxygen à préciser):

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_ est là pour vous aider avec namespacing (pour éviter les conflits entre enums) et les doubles underscores aident à distinguer entre les valeurs réelles et les sentinelles d'itération.

Sidenote:

La rédaction de ce que je commence l'inquiétude au sujet de la double underscore ne pas être autorisé pour identifier l'utilisateur (il est la mise en œuvre réservée, IIRC?), Mais je ne suis pas encore touché un problème (après 5-6 années de codage dans ce style). C'est peut-être correct car je mets toujours tous mes types dans un espace de noms pour éviter de polluer et d'interférer avec l'espace de noms global.

Questions connexes