213 votes

Comment utiliser les enums comme drapeaux en C++ ?

Traitement de enum en tant que drapeaux fonctionne bien en C# via l'option [Flags] mais quelle est la meilleure façon de le faire en C++ ?

Par exemple, j'aimerais écrire :

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};

seahawk.flags = CanFly | EatsFish | Endangered;

Cependant, j'obtiens des erreurs de compilation concernant int / enum conversions. Existe-t-il un moyen plus agréable d'exprimer cela que le simple casting brutal ? De préférence, je ne veux pas m'appuyer sur des constructions de bibliothèques tierces telles que boost ou Qt.

EDIT : Comme indiqué dans les réponses, je peux éviter l'erreur de compilation en déclarant seahawk.flags como int . Cependant, j'aimerais disposer d'un mécanisme permettant d'assurer la sécurité des types, afin que quelqu'un ne puisse pas écrire seahawk.flags = HasMaximizeButton .

0 votes

Pour autant que je sache, dans Visual C++ 2013, la fonction [Flags] fonctionne très bien, par exemple : [Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};

0 votes

@rivanov, Non cela ne fonctionne pas avec C++ (2015 aussi). Voulez-vous dire C# ?

6 votes

@rivanov, L'attribut [Flags] ne fonctionne qu'avec le Framework .Net dans C++CLI, le C++ natif ne supporte pas de tels attributs.

275voto

eidolon Points 801

La méthode "correcte" consiste à définir des opérateurs binaires pour l'enum, comme :

enum AnimalFlags
{
    HasClaws   = 1,
    CanFly     = 2,
    EatsFish   = 4,
    Endangered = 8
};

inline AnimalFlags operator|(AnimalFlags a, AnimalFlags b)
{
    return static_cast<AnimalFlags>(static_cast<int>(a) | static_cast<int>(b));
}

Etc. le reste des opérateurs de bits. Modifier si nécessaire si l'étendue de l'enum dépasse l'étendue de l'int.

44 votes

^ ceci. La seule question est de savoir comment automatiser/templatiser les définitions des opérateurs pour ne pas avoir à les définir constamment à chaque fois que l'on ajoute une nouvelle énumération.

13 votes

De même, le transfert d'un int arbitraire vers le type enum est-il valide, même si la valeur int ne correspond à aucun des identifiants de l'enum ?

1 votes

Je peux l'utiliser comme a = (a | flag) mais pas comme a |= flag Que dois-je faire à ce sujet ?

138voto

WoutervD Points 542

Note (également un peu hors sujet) : Une autre façon de faire des drapeaux uniques peut être faite en utilisant un décalage de bit. Pour ma part, je trouve cette méthode plus facile à lire.

enum Flags
{
    A = 1 << 0, // binary 0001
    B = 1 << 1, // binary 0010
    C = 1 << 2, // binary 0100
    D = 1 << 3  // binary 1000
};

Il peut contenir des valeurs allant jusqu'à un int, c'est-à-dire, la plupart du temps, 32 drapeaux, ce qui se reflète clairement dans le montant du décalage.

0 votes

En quoi cela n'est-il pas équivalent à utiliser simplement 1, 2, 4 et 8 ?

5 votes

Pourriez-vous supprimer la dernière virgule (3,) et ajouter un deux-points après } pour que le code soit facile à copier et à coller ? Merci

7 votes

Aucune mention de l'hexadécimal ? Blasphème !

59voto

user720594 Points 140

Pour les personnes paresseuses comme moi, voici une solution modèle pour le copier-coller :

template<class T> inline T operator~ (T a) { return (T)~(int)a; }
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
template<class T> inline T& operator|= (T& a, T b) { return (T&)((int&)a |= (int)b); }
template<class T> inline T& operator&= (T& a, T b) { return (T&)((int&)a &= (int)b); }
template<class T> inline T& operator^= (T& a, T b) { return (T&)((int&)a ^= (int)b); }

29 votes

+1 La paresse est l'une des trois grandes vertus d'un programmeur : troisvirtues.com

13 votes

C'est une très bonne solution, mais il faut faire attention à ce qu'elle fournisse joyeusement des opérations bit à bit pour n'importe quel type. J'utilise quelque chose de similaire, mais avec l'ajout de traits identifiant les types auxquels je veux qu'il s'applique, combiné avec un peu de magie enable_if.

0 votes

@Rai : vous pouvez toujours le mettre dans un espace de nom et using le cas échéant, tout comme rel_ops .

56voto

Simon Mourier Points 49585

Notez que si vous travaillez dans l'environnement Windows, il y a une DEFINE_ENUM_FLAG_OPERATORS définie dans winnt.h qui fait le travail pour vous. Donc dans ce cas, vous pouvez faire ceci :

enum AnimalFlags
{
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8
};
DEFINE_ENUM_FLAG_OPERATORS(AnimalFlags)

seahawk.flags = CanFly | EatsFish | Endangered;

2 votes

Il convient de mentionner que DEFINE_ENUM_FLAG_OPERATORS La macro fonctionne également pour C++11 enum class macros, fournissant une solution complète aux drapeaux typés.

48voto

Brian R. Bondy Points 141769

Quel est le type de la variable seahawk.flags ?

Dans le C++ standard, les énumérations ne sont pas sûres au niveau du type. Elles sont effectivement des entiers.

AnimalFlags ne doit PAS être le type de votre variable. Votre variable doit être int et l'erreur disparaîtra.

Il n'est pas nécessaire de mettre des valeurs hexadécimales comme l'ont suggéré d'autres personnes. Cela ne fait aucune différence.

Les valeurs de l'enum sont de type int par défaut. Ainsi, vous pouvez sûrement les combiner par OU binaire, les assembler et stocker le résultat dans un int.

Le type énumération est un sous-ensemble restreint de int dont la valeur est l'une de ses valeurs énumérées. Par conséquent, lorsque vous créez une nouvelle valeur en dehors de cette plage, vous ne pouvez pas l'affecter sans faire un casting vers une variable de votre type enum.

Vous pouvez également modifier les types de valeurs de l'enum si vous le souhaitez, mais cette question n'a aucun intérêt.

EDIT : Le posteur a dit qu'il était préoccupé par la sécurité des types et qu'il ne voulait pas d'une valeur qui ne devrait pas exister dans le type int.

Mais il serait dangereux de mettre une valeur en dehors de la plage de AnimalFlags dans une variable de type AnimalFlags.

Il existe un moyen sûr de vérifier les valeurs hors limites à l'intérieur du type int...

int iFlags = HasClaws | CanFly;
//InvalidAnimalFlagMaxValue-1 gives you a value of all the bits 
// smaller than itself set to 1
//This check makes sure that no other bits are set.
assert(iFlags & ~(InvalidAnimalFlagMaxValue-1) == 0);

enum AnimalFlags {
    HasClaws = 1,
    CanFly =2,
    EatsFish = 4,
    Endangered = 8,

    // put new enum values above here
    InvalidAnimalFlagMaxValue = 16
};

Ce qui précède ne vous empêche pas de mettre un drapeau invalide d'un enum différent qui a la valeur 1, 2, 4 ou 8.

Si vous voulez une sécurité de type absolue, vous pouvez simplement créer un std::set et y stocker chaque drapeau. Ce n'est pas efficace en termes d'espace, mais c'est sûr au niveau des types et cela vous donne les mêmes possibilités qu'un int bitflag.

Note C++0x : Enums fortement typés

En C++0x, il est enfin possible d'avoir des valeurs d'énumération sans risque pour les types.....

enum class AnimalFlags {
    CanFly = 2,
    HasClaws = 4
};

if(CanFly == 2) { }//Compiling error

4 votes

Les valeurs de l'enum ne sont pas des entiers, mais elles se convertissent très facilement en entiers. Le type de HasClaws | CanFly est un certain type d'entier, mais le type de HasClaws es AnimalFlags n'est pas un type d'entier.

1 votes

Ah, mais que se passe-t-il si nous définissons la plage correcte de l'enum comme étant non seulement les valeurs individuelles des drapeaux mais aussi leurs combinaisons bit à bit. Alors la réponse d'eidolon est correcte, et maintient que seules les combinaisons de l'enum de drapeaux correctes peuvent être passées comme ce type.

3 votes

@Scott : Il est intéressant de noter que la norme C++ définit la plage de valeurs valides d'une instance d'enum de cette façon. "pour une énumération où emin est le plus petit énumérateur et emax est le plus grand, les valeurs de l'énumération sont les valeurs dans l'intervalle bmin à bmax , dénommées comme suit : Soit K 1 pour une représentation en complément à deux et 0 pour une représentation en complément à un ou en signe-magnitude. bmax est la plus petite valeur supérieure ou égale à max(|emin| K, |emax|) et égale à (1u<<M) - 1 , donde M est un entier non-négatif."

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X