Un enum X : int
(C #) ou enum class X : int
(C ++ 11) est un type qui a un champ interne caché int
pouvant contenir n'importe quelle valeur. De plus, un nombre de constantes prédéfinies de X
sont définies sur l'énum. Il est possible de convertir l'énum en son entier et inversement. Ceci est vrai à la fois en C # et en C ++ 11.
En C #, les énumérations ne sont pas seulement utilisées pour conserver des valeurs individuelles, mais également pour contenir des combinaisons d'indicateurs au niveau du bit, conformément à la recommandation de Microsoft . Ces énumérations sont (généralement, mais pas nécessairement) décorées avec l' [Flags]
attribut. Pour faciliter la vie des développeurs, les opérateurs au niveau des bits (OR, AND, etc ...) sont surchargés afin que vous puissiez facilement faire quelque chose comme ça (C #):
void M(NumericType flags);
M(NumericType.Sign | NumericType.ZeroPadding);
Je suis un développeur C # expérimenté, mais je ne programme C ++ que depuis quelques jours maintenant et je ne suis pas connu des conventions C ++. J'ai l'intention d'utiliser un enum C ++ 11 exactement de la même manière que j'avais l'habitude de le faire en C #. En C ++ 11, les opérateurs de bits sur les énumérations étendues ne sont pas surchargés. Je voulais donc les surcharger .
Cela a suscité un débat et les opinions semblent varier entre trois options:
Une variable du type enum est utilisée pour contenir le champ de bits, similaire à C #:
void M(NumericType flags); // With operator overloading: M(NumericType::Sign | NumericType::ZeroPadding); // Without operator overloading: M(static_cast<NumericType>(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding)));
Mais cela irait à l’encontre de la philosophie d’énumération fortement typée des énumérations étendues de C ++ 11.
Utilisez un entier simple si vous souhaitez stocker une combinaison d'énums au niveau du bit:
void M(int flags); M(static_cast<int>(NumericType::Sign) | static_cast<int>(NumericType::ZeroPadding));
Mais cela réduirait tout à un
int
, ne vous laissant aucune idée du type que vous êtes censé mettre dans la méthode.Ecrivez une classe séparée qui surchargera les opérateurs et maintiendra les indicateurs au niveau du bit dans un champ entier caché:
class NumericTypeFlags { unsigned flags_; public: NumericTypeFlags () : flags_(0) {} NumericTypeFlags (NumericType t) : flags_(static_cast<unsigned>(t)) {} //...define BITWISE test/set operations }; void M(NumericTypeFlags flags); M(NumericType::Sign | NumericType::ZeroPadding);
( code complet par utilisateur315052 )
Mais alors vous n’avez pas d’IntelliSense ni aucun autre support pour vous indiquer les valeurs possibles.
Je sais que c’est une question subjective , mais: quelle approche dois-je utiliser? Quelle approche, le cas échéant, est la plus largement reconnue en C ++? Quelle approche utilisez-vous pour traiter les champs de bits et pourquoi ?
Bien sûr, étant donné que les trois approches fonctionnent, je cherche des raisons factuelles et techniques, des conventions généralement acceptées, et pas simplement des préférences personnelles.
Par exemple, à cause de mon arrière-plan C #, j'ai tendance à choisir l'approche 1 en C ++. Cela présente l'avantage supplémentaire que mon environnement de développement peut m'indiquer les valeurs possibles. Avec les opérateurs d'enum surchargés, il est facile à écrire et à comprendre, et plutôt propre. Et la signature de la méthode indique clairement le type de valeur qu’elle attend. Mais la plupart des gens ici ne sont pas d'accord avec moi, probablement pour une bonne raison.
la source
enum E { A = 1, B = 2, C = 4, };
, la plage est0..7
(3 bits). Ainsi, la norme C ++ garantit explicitement que # 1 sera toujours une option viable. [Spécifiquement, sauf si spécifié autrement , parenum class
défautenum class : int
, et a donc toujours un type sous-jacent fixe.])Réponses:
Le moyen le plus simple consiste à fournir à l’opérateur les surcharges vous-même. Je songe à créer une macro pour développer les surcharges de base par type.
(Notez qu'il
type_traits
s'agit d'un en-tête C ++ 11 et d'std::underlying_type_t
une fonctionnalité C ++ 14.)la source
static_cast<T>
pour la saisie, mais la conversion de style C pour le résultat ici?SBJFrameDrag
est défini dans une classe et que l'|
opérateur est utilisé ultérieurement dans les définitions de la même classe, comment définiriez-vous l'opérateur de sorte qu'il puisse être utilisé dans la classe?Historiquement, j'aurais toujours utilisé l'ancienne énumération (faiblement typée) pour nommer les constantes de bits, et simplement utilisé explicitement la classe de stockage pour stocker l'indicateur résultant. Ici, c’est à moi qu’il incombe de s’assurer que mes énumérations correspondent au type de stockage et de garder une trace de l’association entre le champ et ses constantes connexes.
J'aime l'idée des énumérations fortement typées, mais je ne suis pas très à l'aise avec l'idée que les variables de type énuméré puissent contenir des valeurs qui ne figurent pas parmi les constantes de cette énumération.
Par exemple, en supposant que le bitwise ou a été surchargé:
Pour votre 3ème option, vous avez besoin d'un passe-partout pour extraire le type de stockage de l'énumération. En supposant que nous voulions forcer un type sous-jacent non signé (nous pouvons aussi manipuler signé, avec un peu plus de code):
Cela ne vous donne toujours pas IntelliSense ni l'auto-complétion, mais la détection du type de stockage est moins laide que prévu à l'origine.
Maintenant, j'ai trouvé une alternative: vous pouvez spécifier le type de stockage pour une énumération faiblement typée. Il a même la même syntaxe qu'en C #
Comme il est faiblement typé et qu'il convertit implicitement vers / depuis int (ou le type de stockage que vous choisissez), il est moins étrange d'avoir des valeurs qui ne correspondent pas aux constantes énumérées.
L'inconvénient est que cela est décrit comme "transitoire" ...
NB cette variante ajoute ses constantes énumérées aux portées imbriquée et englobante, mais vous pouvez contourner ce problème avec un espace de noms:
la source
Vous pouvez définir des indicateurs d'énumération sécurisés pour le type dans C ++ 11 à l'aide de
std::enable_if
. Ceci est une implémentation rudimentaire qui manque peut-être à certaines choses:Notez que lenumber_of_bits
compilateur ne peut malheureusement pas remplir le formulaire, car C ++ ne dispose d'aucun moyen d'introspection quant aux valeurs possibles d'une énumération.Edit: En fait, je suis corrigé, il est possible de remplir le compilateur
number_of_bits
pour vous.Notez que cela peut gérer (de manière inefficace) une plage de valeurs enum non continue. Disons simplement que ce n'est pas une bonne idée d'utiliser ce qui précède avec une telle énumération, sinon la folie s'ensuivra:
Mais toutes choses considérées, c’est finalement une solution tout à fait utilisable. N'a pas besoin de bidouillage côté utilisateur, il est sécuritaire en ce qui concerne les types et aussi efficace qu'il soit (j'appuie fortement sur la
std::bitset
qualité de la mise en oeuvre ici;)
).la source
je
hainedéteste les macros dans mon C ++ 14 autant que le prochain, mais je me suis mis à l’utiliser partout, et assez généreusement aussi:Faire usage aussi simple que
Et, comme on dit, la preuve est dans le pudding:
N'hésitez pas à choisir l'un des opérateurs individuels comme bon vous semble, mais à mon avis, le C / C ++ sert d'interface avec les concepts et les flux de bas niveau, et vous pouvez les extraire de mes mains mortes et froides. et je vais vous battre avec toutes les macros impies et les sorts peu renversants que je peux conjurer pour les garder.
la source
std::enable_if
avecstd::is_enum
pour limiter les surcharges de votre opérateur libre au travail avec les types énumérés. J'ai également ajouté des opérateurs de comparaison (usingstd::underlying_type
) et l'opérateur non logique pour combler davantage l'écart sans perdre le typage fort. La seule chose que je ne peux pas répondre est la conversion implicite bool, maisflags != 0
et!flags
sont suffisantes pour moi.Généralement, vous définissez un ensemble de valeurs entières correspondant à des nombres binaires définis sur un seul bit, puis vous les additionnez. C’est ainsi que les programmeurs C le font habituellement.
Donc vous auriez (en utilisant l'opérateur bitshift pour définir les valeurs, par exemple 1 << 2 est identique à binaire 100)
etc
En C ++, vous avez plus d’options, définissez un nouveau type plutôt qu’un int (use typedef ) et définissez les mêmes valeurs que ci-dessus; ou définir un bitfield ou un vecteur de bools . Les deux derniers sont très efficaces en termes d'espace et sont beaucoup plus utiles pour gérer les drapeaux. Un champ de bits a l'avantage de vous donner la vérification de type (et donc, intellisense).
Je dirais (évidemment subjectif) qu'un programmeur C ++ devrait utiliser un champ de bits pour votre problème, mais j'ai tendance à voir l'approche #define utilisée par les programmes C beaucoup dans les programmes C ++.
Je suppose que le champ de bits est le plus proche de l'énumération de C #. Pourquoi C # a-t-il essayé de surcharger un énum pour qu'il soit un type de champ de bits?
la source
0b0100
) afin que le1 << n
format soit en quelque sorte obsolète.Un court exemple d'énum-flags ci-dessous ressemble beaucoup à C #.
À propos de l'approche, à mon avis: moins de code, moins de bugs, un meilleur code.
ENUM_FLAGS (T) est une macro, définie dans enum_flags.h (moins de 100 lignes, libre d'utilisation sans restrictions).
la source
::type
là. Correction: paste.ubuntu.com/23884820Il y a encore une autre façon de peler le chat:
Au lieu de surcharger les opérateurs de bits, au moins certains préfèreraient peut-être simplement ajouter un liner 4 pour vous aider à contourner cette restriction désagréable des enums étendus:
Certes, vous devez taper la
ut_cast()
chose à chaque fois, mais du côté positif, cela donne un code plus lisible, au même sens que l’utiliserstatic_cast<>()
, par rapport à la conversion de type implicite ou à uneoperator uint16_t()
sorte de chose.Et soyons honnêtes ici, utiliser le type
Foo
comme dans le code ci-dessus présente des dangers:Quelque part, quelqu'un pourrait faire un changement de casse sur une variable
foo
et ne pas s'attendre à ce qu'il contienne plus d'une valeur ...Donc, jeter un peu de code dans le code
ut_cast()
aide à prévenir les lecteurs que quelque chose de louche se passe.la source