2012-12-20 5 views
4

Je suis un grand fan des fonctionnalités de typage fort de C++ et ce que j'aime le plus, c'est d'utiliser des énumérations tout en traitant des ensembles de données limités.Utiliser enum dans les boucles et la cohérence des valeurs

Mais énumérations manque quelques fonctionnalités utiles, par exemple les opérateurs:

enum class Hex : int 
{ 
    n00, n01, n02, n03, 
    n04, n05, n06, n07, 
    n08, n09, n10, n11, 
    n12, n13, n14, n15 
}; 

for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Oops! no 'operator ++' 
{ /* ... */ } 

est facile de se débarrasser de l'absence d'opérateurs créant un opérateur libre sur la même portée:

Hex &operator ++(Hex &h) 
{ 
    int r = static_cast<int>(Hex); 
    h = static_cast<Hex>(r + 1); 
    return h; 
} 

for (Hex h = Hex::n0; h <= Hex::n15; ++h) // Now the '++h' works! 
{ 
    std::cout << std::dec << int(h) << ": " 
       << std::hex << int(h) << '\n'; 
} 

Mais cette approche est plus une nuisance qu'une solution, car elle peut casser la limitation de valeur de l'énumération: l'application ++h tandis que h est égale à Hex::n15 mettra h à la valeur 16, qui est sur le Hex portée des valeurs tandis que h est toujours du type Hex, Ce problème est plus évident dans d'autres énumérations:

enum class Prime : int 
{ 
    n00 = 2, n01 = 3, n02 = 5, n03 = 7, 
    n04 = 11, n05 = 13, n06 = 17, n07 = 19, 
    n08 = 23, n09 = 29, n10 = 31, n11 = 37, 
    n12 = 41, n13 = 43, n14 = 47, n15 = 53 
}; 

Prime &operator ++(Prime &p) 
{ 
    // How to implement this?! will I need a lookup table? 
    return p; 
} 

Ce problème a été une surprise pour moi; Je pensais que stocker une valeur incorrecte dans une valeur d'énumération jetterait une exception. Donc, pour l'instant, je me demandais s'il y a une façon élégante de traiter les faiblesses de cette énumération, les objectifs que je veux atteindre sont les suivants:

  • trouver un moyen confortable d'utiliser des valeurs d'énumération dans les boucles.
  • Assurer la cohérence des données d'énumération entre les opérations.

Questions supplémentaires:

  • Y at-il une raison de ne pas lancer une exception lorsqu'une données d'énumération obtient une valeur qui est hors de ses valeurs possibles?
  • Il existe un moyen de déduire le type associé à une classe d'énumération ?, le type int dans les énumérations Hex et Prime.
+2

Si vous souhaitez parcourir, n'utilisez pas d'énumérations. – Pubby

+0

La fuite de fonction est en effet assez dangereuse ... –

+2

On peut dire que le premier problème se manifeste seulement parce que vous êtes absents des énumérations - vous les utilisez pour énumérer les entiers! C'est mieux fait avec des entiers réels. Le deuxième problème (énumération des nombres premiers) est non trivial. –

Répondre

1

Vous pouvez utiliser un switch:

class Invalid {}; 
Prime& operator ++(Prime& p) 
{ 
    switch(p) 
    { 
     case n00: return n01; 
     case n01: return n02; 
     case n02: return n03; 
     case n03: return n04; 
     case n04: return n05; 
     case n05: return n06; 
     case n06: return n07; 
     case n07: return n08; 
     case n08: return n09; 
     case n09: return n10; 
     case n10: return n11; 
     case n11: return n12; 
     case n12: return n13; 
     case n13: return n14; 
     case n14: return n15; 
     // Here: 2 choices: loop or throw (which is the only way to signal an error here) 
     case n15: default: throw Invalid(); 
    } 
} 

Mais notez que c'est pas la bonne utilisation des énumérations. Personnellement, je trouve cette erreur-sujettes. Si vous voulez énumérer des entiers, vous pouvez utiliser un tableau d'ints pour le faire, ou pour les nombres premiers, une fonction (au sens mathématique: int nextPrime(int)).

+0

'if (p! = N15) return (Hex) ((int) p + 1); lancer Invalid(); –

+0

Non. Ex: pour 'p = n09',' (int) p == 29', donc '(int) p + 1 == 30', et vous n'avez aucune valeur enum dans' Prime' qui a la valeur de 30. – Synxis

+0

Désolé, je suis un idiot. Je n'ai pas lu la seconde partie de la question et je ne pensais qu'à «Hex». –

4

Comme vous l'avez remarqué, enum en C++ est pas un type énuméré, mais quelque chose de plus complexe (ou plus mixte). Lorsque vous définissez une enum, vous définissez en fait deux choses:

  1. Un type intégral avec une gamme juridique suffisante pour contenir un ou de toutes les valeurs énumérées. (Techniquement: la plage est 2^n - 1, où n est le nombre de bits nécessaires pour contenir la plus grande valeur.

  2. Une série de constantes nommées ayant le type nouvellement défini.

(je ne sais pas ce qui se passe en ce qui concerne la plage si vous spécifier explicitement un type sous-jacent.)

Compte tenu de votre enum Prime, par exemple, les valeurs légales seraient tous les entiers dans la plage [0...64), même si toutes ces valeurs n'ont pas de nom. (Au moins, si vous ne l'avez pas spécifiquement dire que ce devrait être un int.)

Il est possible de mettre en œuvre un itérateur pour énumérations sans initializers; J'ai un programme qui génère le code nécessaire. Mais cela fonctionne en maintenant la valeur dans un type intégral qui est assez grand pour contenir la valeur maximale plus un. Mes implémentations générées par la machine de ++ sur une telle énumération assert si vous essayez d'incrémenter au-delà de la fin. (Notez que votre premier exemple, il faudrait itérer h un au-delà de la dernière valeur. Ma mise en œuvre des différents opérateurs n'a pas permettent, ce qui est la raison pour laquelle j'utilise un itérateur)

Quant à savoir pourquoi C++ prend en charge l'étendue gamme: enum sont souvent utilisés pour définir des masques de bits:

enum X 
{ 
    a = 0x01, 
    b = 0x02, 
    c = 0x04, 
    all = a | b | c, 
    none = 0 
}; 

X operator|(X lhs, X rhs) 
{ 
    return X((int)lhs | (int)rhs); 
} 
// similarly, &, |= and &=, and maybe ~ 

on pourrait faire valoir que cette utilisation serait mieux gérée par une classe, mais l'utilisation de enum car il est omniprésent.

(FWIW: mon générateur de code ne génère pas les ++, -- et itérateurs si l'une des valeurs ENUM a une valeur définie explicitement, et ne génère pas |, & etc. à moins que toutes les valeurs ont valeurs explicitement définies)

Quant à savoir pourquoi il n'y a pas d'erreur lorsque vous convertissez une valeur en dehors la gamme juridique (par exemple 100, pour X, ci-dessus) est tout simplement conforme avec la philosophie générale héritée de C:. il vaut mieux être rapide que de être correct. Effectuer une vérification de plage supplémentaire entraînerait des coûts d'exécution supplémentaires de . Enfin en ce qui concerne votre dernier exemple: Je ne vois pas cela comme une utilisation réaliste de enum. La solution correcte est ici un int[]. Alors que le C++ enum est plutôt une race mixte, je l'utiliserais seulement comme un type énuméré réel, ou pour des masques binaires (et seulement pour les masques binaires car c'est un idiome très répandu de ).

+1

C++ 11 énumérations avec le type sous-jacent spécifié ont bien sûr la plage du type sous-jacent. – PlasmaHH

+0

@PlasmaHH Cela semble logique, mais on ne sait jamais sans réellement le vérifier. (Je ne vois aucune raison d'utiliser un 'enum' si je veux forcer un type sous-jacent spécifique.) –

+0

cela a probablement du sens lorsque vous utilisez des énumérations comme constantes pour les masques de bits. – PlasmaHH

Questions connexes