web-dev-qa-db-fra.com

Comment utiliser les énumérations comme drapeaux en C ++?

Traiter enums en tant que drapeaux fonctionne bien en C # via le [Flags] attribut, mais quelle est la meilleure façon de faire cela en C++?

Par exemple, j'aimerais écrire:

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

seahawk.flags = CanFly | EatsFish | Endangered;

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

EDIT: Comme indiqué dans les réponses, je peux éviter l’erreur du compilateur en déclarant seahawk.flags comme int. Cependant, j'aimerais avoir un mécanisme pour appliquer la sécurité de type, afin que personne ne puisse écrire seahawk.flags = HasMaximizeButton.

166
Tony the Pony

La méthode "correcte" consiste à définir des opérateurs de bits pour l'énumération, comme suit:

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 reste des opérateurs de bits. Modifiez si nécessaire si la plage d'enum dépasse la plage int.

221
eidolon

Remarque (également un peu hors sujet): Un autre moyen de créer des drapeaux uniques peut être utilisé en utilisant un décalage de bit. Je trouve moi-même cela 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 que la plupart du temps 32 indicateurs sont clairement reflétés dans le nombre de postes.

114
WoutervD

Pour les paresseux comme moi, voici une solution à copier-coller basée sur un modèle:

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

Quel type est la variable seahawk.flags?

En C++ standard, les énumérations ne sont pas sécurisées par le type. Ils sont effectivement des entiers.

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

Mettre les valeurs hexadécimales comme certaines personnes suggérées n’est pas nécessaire, cela ne fait aucune différence.

Les valeurs enum sont ARE de type int par défaut. Donc vous pouvez sûrement bitwise OR les combiner et les assembler et stocker le résultat dans un int.

Le type enum 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 transtyper une variable de votre type enum.

Vous pouvez également modifier les types de valeur enum si vous le souhaitez, mais cette question ne sert à rien.

EDIT: L’affiche disait qu’ils étaient préoccupés par la sécurité du type et qu’ils ne souhaitaient pas une valeur qui ne devrait pas exister dans le type int.

Mais il serait de type dangereux de placer 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 indicateur non valide provenant d’une énumération différente ayant 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 stocker chaque drapeau à l'intérieur. Ce n'est pas efficace en termes d'espace, mais il est de type sûr et vous offre les mêmes capacités qu'un int.

note C++ 0x: enums fortement typés

En C++ 0x, vous pouvez enfin avoir des valeurs de type safe enum ....

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

if(CanFly == 2) { }//Compiling error
41
Brian R. Bondy

Notez que si vous travaillez dans un environnement Windows, il y a un DEFINE_ENUM_FLAG_OPERATORS macro 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;
39
Simon Mourier

Je trouve la réponse actuellement acceptée de eidolon trop dangereuse. L'optimiseur du compilateur peut émettre des hypothèses sur les valeurs possibles de l'énum et vous pouvez obtenir des erreurs avec des valeurs non valides. Et en général, personne ne veut définir toutes les permutations possibles dans les énumérations de drapeaux.

Comme Brian R. Bondy l'indique ci-dessous, si vous utilisez C++ 11 (ce que tout le monde devrait, c'est aussi bien), vous pouvez maintenant le faire plus facilement avec enum class:

enum class ObjectType : uint32_t
{
    ANIMAL = (1 << 0),
    VEGETABLE = (1 << 1),
    MINERAL = (1 << 2)
};


constexpr enum ObjectType operator |( const enum ObjectType selfValue, const enum ObjectType inValue )
{
    return (enum ObjectType)(uint32_t(selfValue) | uint32_t(inValue));
}

// ... add more operators here. 

Cela garantit une taille et une plage de valeurs stables en spécifiant un type pour l’énumération, empêche le downcasting automatique des énumérations en ints, etc. en utilisant enum class, et utilise constexpr pour s’assurer que le code des opérateurs est en ligne et donc aussi rapide que les nombres normaux.

Pour les personnes coincées avec des dialectes pré-11 C++

Si j'étais coincé avec un compilateur qui ne prend pas en charge C++ 11, je choisirais d'encapsuler un type int dans une classe qui n'autorise ensuite que l'utilisation des opérateurs au niveau du bit et des types de cette énumération pour définir ses valeurs:

template<class ENUM,class UNDERLYING=typename std::underlying_type<ENUM>::type>
class SafeEnum
{
public:
    SafeEnum() : mFlags(0) {}
    SafeEnum( ENUM singleFlag ) : mFlags(singleFlag) {}
    SafeEnum( const SafeEnum& original ) : mFlags(original.mFlags) {}

    SafeEnum&   operator |=( ENUM addValue )    { mFlags |= addValue; return *this; }
    SafeEnum    operator |( ENUM addValue )     { SafeEnum  result(*this); result |= addValue; return result; }
    SafeEnum&   operator &=( ENUM maskValue )   { mFlags &= maskValue; return *this; }
    SafeEnum    operator &( ENUM maskValue )    { SafeEnum  result(*this); result &= maskValue; return result; }
    SafeEnum    operator ~()    { SafeEnum  result(*this); result.mFlags = ~result.mFlags; return result; }
    explicit operator bool()                    { return mFlags != 0; }

protected:
    UNDERLYING  mFlags;
};

Vous pouvez définir cela à peu près comme un enum + typedef:

enum TFlags_
{
    EFlagsNone  = 0,
    EFlagOne    = (1 << 0),
    EFlagTwo    = (1 << 1),
    EFlagThree  = (1 << 2),
    EFlagFour   = (1 << 3)
};

typedef SafeEnum<enum TFlags_>  TFlags;

Et l'utilisation est similaire:

TFlags      myFlags;

myFlags |= EFlagTwo;
myFlags |= EFlagThree;

if( myFlags & EFlagTwo )
    std::cout << "flag 2 is set" << std::endl;
if( (myFlags & EFlagFour) == EFlagsNone )
    std::cout << "flag 4 is not set" << std::endl;

Et vous pouvez également remplacer le type sous-jacent pour les énumérations à stabilité binaire (telles que enum foo : type) en utilisant le deuxième paramètre de modèle, à savoir typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

J'ai marqué le operator bool _ écrasez-vous avec le mot clé explicit de C++ 11 pour l'empêcher de générer des conversions int, car elles risqueraient de réduire les ensembles d'indicateurs à 0 ou 1 lors de leur écriture. Si vous ne pouvez pas utiliser C++ 11, laissez cette surcharge et réécrivez la première condition de l'exemple d'utilisation comme (myFlags & EFlagTwo) == EFlagTwo.

22
uliwitness

Le moyen le plus simple de procéder comme indiqué ici , en utilisant la classe de bibliothèque standard bitset .

Pour émuler la fonctionnalité C # de manière sécurisée, vous devez écrire un wrapper de modèle autour du jeu de bits, en remplaçant les arguments int par un enum donné comme paramètre de type au modèle. Quelque chose comme:

    template <class T, int N>
class FlagSet
{

    bitset<N> bits;

    FlagSet(T enumVal)
    {
        bits.set(enumVal);
    }

    // etc.
};

enum MyFlags
{
    FLAG_ONE,
    FLAG_TWO
};

FlagSet<MyFlags, 2> myFlag;
17
soru

À mon avis, aucune des réponses à ce jour n'est idéale. Pour être idéal, j'attendrais la solution:

  1. Soutenez le ==, !=, =, &, &=, |, |= et ~ opérateurs au sens conventionnel (ie a & b)
  2. Protégez votre texte, c’est-à-dire qu’il ne permet pas d’attribuer des valeurs non énumérées, telles que des littéraux ou des types entiers (à l’exception des combinaisons binaires de valeurs énumérées), ni d’attribuer une variable enum à un type entier.
  3. Autoriser des expressions telles que if (a & b)...
  4. Pas besoin de macros maléfiques, de fonctionnalités spécifiques à l'implémentation ou d'autres hacks

La plupart des solutions proposées jusqu'ici se retrouvent aux points 2 ou 3. Selon moi, WebDancer est la solution la plus proche, mais échoue au point 3 et doit être répétée à chaque énumération.

Ma solution proposée est une version généralisée de WebDancer qui aborde également le point 3:

#include <cstdint>
#include <type_traits>

template<typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
class auto_bool
{
    T val_;
public:
    constexpr auto_bool(T val) : val_(val) {}
    constexpr operator T() const { return val_; }
    constexpr explicit operator bool() const
    {
        return static_cast<std::underlying_type_t<T>>(val_) != 0;
    }
};

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr auto_bool<T> operator&(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) &
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

template <typename T = typename std::enable_if<std::is_enum<T>::value, T>::type>
constexpr T operator|(T lhs, T rhs)
{
    return static_cast<T>(
        static_cast<typename std::underlying_type<T>::type>(lhs) |
        static_cast<typename std::underlying_type<T>::type>(rhs));
}

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

enum class PlantFlags : uint8_t
{
    HasLeaves = 1,
    HasFlowers = 2,
    HasFruit = 4,
    HasThorns = 8
};

int main()
{
    AnimalFlags seahawk = AnimalFlags::CanFly;        // Compiles, as expected
    AnimalFlags lion = AnimalFlags::HasClaws;         // Compiles, as expected
    PlantFlags rose = PlantFlags::HasFlowers;         // Compiles, as expected
//  rose = 1;                                         // Won't compile, as expected
    if (seahawk != lion) {}                           // Compiles, as expected
//  if (seahawk == rose) {}                           // Won't compile, as expected
//  seahawk = PlantFlags::HasThorns;                  // Won't compile, as expected
    seahawk = seahawk | AnimalFlags::EatsFish;        // Compiles, as expected
    lion = AnimalFlags::HasClaws |                    // Compiles, as expected
           AnimalFlags::Endangered;
//  int eagle = AnimalFlags::CanFly |                 // Won't compile, as expected
//              AnimalFlags::HasClaws;
//  int has_claws = seahawk & AnimalFlags::CanFly;    // Won't compile, as expected
    if (seahawk & AnimalFlags::CanFly) {}             // Compiles, as expected
    seahawk = seahawk & AnimalFlags::CanFly;          // Compiles, as expected

    return 0;
}

Cela crée des surcharges des opérateurs nécessaires mais utilise SFINAE pour les limiter aux types énumérés. Notez que par souci de brièveté, je n'ai pas défini tous les opérateurs, mais le seul qui soit différent est le &. Les opérateurs sont actuellement globaux (c'est-à-dire qu'ils s'appliquent à tous les types énumérés), mais cela pourrait être réduit en plaçant les surcharges dans un espace de noms (ce que je fais), ou en ajoutant des conditions SFINAE supplémentaires (en utilisant éventuellement des types sous-jacents particuliers ou des alias de types créés spécialement). ). underlying_type_t Est une fonctionnalité C++ 14, mais elle semble bien supportée et est facile à émuler pour C++ 11 avec un simple template<typename T> using underlying_type_t = underlying_type<T>::type;.

11
Trevor

Le standard C++ en parle explicitement, voir la section "17.5.2.1.3 Types de masque de bits":

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3485.pdf

Étant donné ce "modèle", vous obtenez:

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

constexpr AnimalFlags operator|(AnimalFlags X, AnimalFlags Y) {
    return static_cast<AnimalFlags>(
        static_cast<unsigned int>(X) | static_cast<unsigned int>(Y));
}

AnimalFlags& operator|=(AnimalFlags& X, AnimalFlags Y) {
    X = X | Y; return X;
}

Et similaire pour les autres opérateurs. Notez également le "constexpr", il est nécessaire si vous voulez que le compilateur puisse exécuter le temps de compilation des opérateurs.

Si vous utilisez C++/CLI et souhaitez pouvoir affecter aux membres enum des classes ref, vous devez utiliser des références de suivi:

AnimalFlags% operator|=(AnimalFlags% X, AnimalFlags Y) {
    X = X | Y; return X;
}

REMARQUE: cet exemple n'est pas complet. Pour plus d'informations sur l'ensemble des opérateurs, reportez-vous à la section "17.5.2.1.3 Types de masque de masque".

6
WebDancer

Je me suis retrouvé à poser la même question et à proposer une solution générique basée sur C++ 11, similaire à celle de soru:

template <typename TENUM>
class FlagSet {

private:
    using TUNDER = typename std::underlying_type<TENUM>::type;
    std::bitset<std::numeric_limits<TUNDER>::max()> m_flags;

public:
    FlagSet() = default;

    template <typename... ARGS>
    FlagSet(TENUM f, ARGS... args) : FlagSet(args...)
    {   
        set(f);
    }   
    FlagSet& set(TENUM f)
    {   
        m_flags.set(static_cast<TUNDER>(f));
        return *this;
    }   
    bool test(TENUM f)
    {   
        return m_flags.test(static_cast<TUNDER>(f));
    }   
    FlagSet& operator|=(TENUM f)
    {   
        return set(f);
    }   
};

L'interface peut être améliorée au goût. Ensuite, il peut être utilisé comme suit:

FlagSet<Flags> flags{Flags::FLAG_A, Flags::FLAG_C};
flags |= Flags::FLAG_D;
6
Omair

Si votre compilateur ne supporte pas encore les énumérations fortement typées, vous pouvez consulter le article suivant depuis la source c ++:

De l'abstrait:

Cet article présente une solution au problème de la limitation des opérations de bits à
n'autorise que ceux qui sont sûrs et légitimes, et transforme toutes les manipulations de bits invalides en erreurs de compilation. Mieux encore, la syntaxe des opérations sur les bits reste inchangée et il n'est pas nécessaire de modifier le code utilisant des bits, sauf éventuellement pour corriger les erreurs qui n'étaient pas encore détectées.

5
Francesco

Je voudrais élaborer sur réponse d'Uliwitness , en fixant son code pour C++ 98 et en utilisant le idiome Safe Bool , faute du std::underlying_type<> template et le mot clé explicit dans les versions C++ inférieures à C++ 11.

Je l'ai également modifié pour que les valeurs enum puissent être séquentielles sans affectation explicite, afin que vous puissiez avoir

enum AnimalFlags_
{
    HasClaws,
    CanFly,
    EatsFish,
    Endangered
};
typedef FlagsEnum<AnimalFlags_> AnimalFlags;

seahawk.flags = AnimalFlags() | CanFly | EatsFish | Endangered;

Vous pouvez alors obtenir la valeur des drapeaux bruts avec

seahawk.flags.value();

Voici le code.

template <typename EnumType, typename Underlying = int>
class FlagsEnum
{
    typedef Underlying FlagsEnum::* RestrictedBool;

public:
    FlagsEnum() : m_flags(Underlying()) {}

    FlagsEnum(EnumType singleFlag):
        m_flags(1 << singleFlag)
    {}

    FlagsEnum(const FlagsEnum& original):
        m_flags(original.m_flags)
    {}

    FlagsEnum& operator |=(const FlagsEnum& f) {
        m_flags |= f.m_flags;
        return *this;
    }

    FlagsEnum& operator &=(const FlagsEnum& f) {
        m_flags &= f.m_flags;
        return *this;
    }

    friend FlagsEnum operator |(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) |= f2;
    }

    friend FlagsEnum operator &(const FlagsEnum& f1, const FlagsEnum& f2) {
        return FlagsEnum(f1) &= f2;
    }

    FlagsEnum operator ~() const {
        FlagsEnum result(*this);
        result.m_flags = ~result.m_flags;
        return result;
    }

    operator RestrictedBool() const {
        return m_flags ? &FlagsEnum::m_flags : 0;
    }

    Underlying value() const {
        return m_flags;
    }

protected:
    Underlying  m_flags;
};
3
Fabio A.

Voici une option pour les masques de bits si vous n’avez pas réellement besoin d’utiliser les valeurs individuelles (par exemple, vous n’avez pas besoin de les désactiver) ... et si vous ne craignez pas de maintenir la compatibilité binaire, c’est-à-dire: ne vous souciez pas de savoir où habitent vos fragments ... ce que vous êtes probablement. Aussi, vous feriez mieux de ne pas trop vous préoccuper de la portée et du contrôle d'accès. Hmmm, les enums ont quelques propriétés intéressantes pour les champs de bits ... je me demande si quelqu'un a déjà essayé cela :)

struct AnimalProperties
{
    bool HasClaws : 1;
    bool CanFly : 1;
    bool EatsFish : 1;
    bool Endangered : 1;
};

union AnimalDescription
{
    AnimalProperties Properties;
    int Flags;
};

void TestUnionFlags()
{
    AnimalDescription propertiesA;
    propertiesA.Properties.CanFly = true;

    AnimalDescription propertiesB = propertiesA;
    propertiesB.Properties.EatsFish = true;

    if( propertiesA.Flags == propertiesB.Flags )
    {
        cout << "Life is terrible :(";
    }
    else
    {
        cout << "Life is great!";
    }

    AnimalDescription propertiesC = propertiesA;
    if( propertiesA.Flags == propertiesC.Flags )
    {
        cout << "Life is great!";
    }
    else
    {
        cout << "Life is terrible :(";
    }
}

Nous pouvons voir que la vie est belle, nous avons nos valeurs discrètes, et nous avons un Nice Int à & and | à nos cœurs le contenu, qui a toujours le contexte de ce que ses bits signifient. Tout est cohérent et prévisible ... pour moi ... tant que je continue à utiliser le compilateur VC++ de Microsoft avec Update 3 sur Win10 x64 et que je ne touche pas à mes drapeaux de compilateur :)

Même si tout est génial ... nous avons un peu de contexte quant à la signification des drapeaux maintenant, puisque c'est dans une union avec le bitfield dans le terrible Dans le monde réel où votre programme peut être responsable de plus d’une tâche distincte, vous pouvez toujours briser accidentellement (assez facilement) deux champs de drapeaux de différentes unions (par exemple, AnimalProperties et ObjectProperties, car ils sont tous les deux ints), en mélangeant vos morceaux, qui est un horrible bug à retracer ... et comment je sais que beaucoup de personnes sur ce post ne travaillent pas très souvent avec des bitmasks, car les construire est facile et les maintenir est difficile.

class AnimalDefinition {
public:
    static AnimalDefinition *GetAnimalDefinition( AnimalFlags flags );   //A little too obvious for my taste... NEXT!
    static AnimalDefinition *GetAnimalDefinition( AnimalProperties properties );   //Oh I see how to use this! BORING, NEXT!
    static AnimalDefinition *GetAnimalDefinition( int flags ); //hmm, wish I could see how to construct a valid "flags" int without CrossFingers+Ctrl+Shift+F("Animal*"). Maybe just hard-code 16 or something?

    AnimalFlags animalFlags;  //Well this is *way* too hard to break unintentionally, screw this!
    int flags; //PERFECT! Nothing will ever go wrong here... 
    //wait, what values are used for this particular flags field? Is this AnimalFlags or ObjectFlags? Or is it RuntimePlatformFlags? Does it matter? Where's the documentation? 
    //Well luckily anyone in the code base and get confused and destroy the whole program! At least I don't need to static_cast anymore, phew!

    private:
    AnimalDescription m_description; //Oh I know what this is. All of the mystery and excitement of life has been stolen away :(
}

Vous devez alors rendre votre déclaration de syndicat privée pour empêcher l’accès direct à "Flags", vous devez ajouter des getters/setters et des surcharges d’opérateurs, puis créer une macro pour tout cela, et vous êtes pratiquement de retour à l’origine faites cela avec un Enum.

Malheureusement, si vous souhaitez que votre code soit portable, je ne pense pas qu'il soit possible de garantir A) ou de B) déterminer la présentation des bits au moment de la compilation (afin que vous puissiez le suivre et au moins corriger les modifications apportées). versions/plates-formes, etc.) décalage dans une structure avec des champs de bits

Au moment de l'exécution, vous pouvez jouer des tours en paramétrant les champs et en XOR sur les drapeaux pour voir quels bits ont changé. Cela me semble plutôt moche, même si les versets offrent une solution cohérente à 100%, indépendante de la plate-forme et totalement déterministe, à savoir: un ENUM.

TL; DR: N'écoutez pas les ennemis. C++ n'est pas l'anglais. Ce n'est pas parce que la définition littérale d'un mot clé abrégé hérité de C ne convient pas à votre utilisation que vous ne devez pas l'utiliser lorsque C et C++ la définition du mot clé inclut absolument votre cas d'utilisation. Vous pouvez également utiliser des structures pour modéliser des éléments autres que les structures et des classes pour des éléments autres que la caste scolaire et la caste sociale. Vous pouvez utiliser float pour les valeurs mises à la terre. Vous pouvez utiliser le caractère pour des variables qui ne sont ni non gravées ni une personne dans un roman, une pièce de théâtre ou un film. Tout programmeur qui consulte le dictionnaire pour déterminer la signification d'un mot clé avant que la spécification de langue ne soit un ... eh bien, je vais retenir ma langue.

Si vous voulez que votre code soit calqué sur le langage parlé, mieux vaut écrire en Objective-C, qui utilise d'ailleurs énormément des énums pour les champs de bits.

3
Steazy

Vous confondez des objets et des collections d'objets. Plus précisément, vous confondez les indicateurs binaires avec des ensembles d’indicateurs binaires. Une solution appropriée ressemblerait à ceci:

// These are individual flags
enum AnimalFlag // Flag, not Flags
{
    HasClaws = 0,
    CanFly,
    EatsFish,
    Endangered
};

class AnimalFlagSet
{
    int m_Flags;

  public:

    AnimalFlagSet() : m_Flags(0) { }

    void Set( AnimalFlag flag ) { m_Flags |= (1 << flag); }

    void Clear( AnimalFlag flag ) { m_Flags &= ~ (1 << flag); }

    bool Get( AnimalFlag flag ) const { return (m_Flags >> flag) & 1; }

};
3
Michael

Actuellement, il n'y a pas de prise en charge linguistique pour les drapeaux enum, les méta-classes peuvent par nature ajouter cette fonctionnalité si elle faisait partie du standard c ++.

Ma solution serait de créer des fonctions de modèle instanciées enum uniquement en ajoutant la prise en charge des opérations au niveau des bits de type en sécurité pour la classe enum utilisant son type sous-jacent:

Fichier: EnumClassBitwise.h

#pragma once
#ifndef _ENUM_CLASS_BITWISE_H_
#define _ENUM_CLASS_BITWISE_H_

#include <type_traits>

//unary ~operator    
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator~ (Enum& val)
{
    val = static_cast<Enum>(~static_cast<std::underlying_type_t<Enum>>(val));
    return val;
}

// & operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator& (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
}

// &= operator
template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator&= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) & static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

//| operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum operator| (Enum lhs, Enum rhs)
{
    return static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
}
//|= operator

template <typename Enum, typename std::enable_if_t<std::is_enum<Enum>::value, int> = 0>
constexpr inline Enum& operator|= (Enum& lhs, Enum rhs)
{
    lhs = static_cast<Enum>(static_cast<std::underlying_type_t<Enum>>(lhs) | static_cast<std::underlying_type_t<Enum>>(rhs));
    return lhs;
}

#endif // _ENUM_CLASS_BITWISE_H_

Pour plus de commodité et pour réduire les erreurs, vous pouvez envelopper les opérations d'indicateurs de bits pour les énumérations et les entiers également:

Fichier: BitFlags.h

#pragma once
#ifndef _BIT_FLAGS_H_
#define _BIT_FLAGS_H_

#include "EnumClassBitwise.h"

 template<typename T>
 class BitFlags
 {
 public:

     constexpr inline BitFlags() = default;
     constexpr inline BitFlags(T value) { mValue = value; }
     constexpr inline BitFlags operator| (T rhs) const { return mValue | rhs; }
     constexpr inline BitFlags operator& (T rhs) const { return mValue & rhs; }
     constexpr inline BitFlags operator~ () const { return ~mValue; }
     constexpr inline operator T() const { return mValue; }
     constexpr inline BitFlags& operator|=(T rhs) { mValue |= rhs; return *this; }
     constexpr inline BitFlags& operator&=(T rhs) { mValue &= rhs; return *this; }
     constexpr inline bool test(T rhs) const { return (mValue & rhs) == rhs; }
     constexpr inline void set(T rhs) { mValue |= rhs; }
     constexpr inline void clear(T rhs) { mValue &= ~rhs; }

 private:
     T mValue;
 };
#endif //#define _BIT_FLAGS_H_

Utilisation possible:

#include <cstdint>
#include <BitFlags.h>
void main()
{
    enum class Options : uint32_t
    { 
          NoOption = 0 << 0
        , Option1  = 1 << 0
        , Option2  = 1 << 1
        , Option3  = 1 << 2
        , Option4  = 1 << 3
    };

    const uint32_t Option1 = 1 << 0;
    const uint32_t Option2 = 1 << 1;
    const uint32_t Option3 = 1 << 2;
    const uint32_t Option4 = 1 << 3;

   //Enum BitFlags
    BitFlags<Options> optionsEnum(Options::NoOption);
    optionsEnum.set(Options::Option1 | Options::Option3);

   //Standard integer BitFlags
    BitFlags<uint32_t> optionsUint32(0);
    optionsUint32.set(Option1 | Option3); 

    return 0;
}
2
Nicker

Voici ma solution sans avoir besoin de beaucoup de surcharge ou de casting:

namespace EFoobar
{
    enum
    {
        FB_A    = 0x1,
        FB_B    = 0x2,
        FB_C    = 0x4,
    };
    typedef long Flags;
}

void Foobar(EFoobar::Flags flags)
{
    if (flags & EFoobar::FB_A)
        // do sth
        ;
    if (flags & EFoobar::FB_B)
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar(EFoobar::FB_A | EFoobar::FB_B);
    EFoobar::Flags otherflags = 0;
    otherflags|= EFoobar::FB_B;
    otherflags&= ~EFoobar::FB_B;
    Foobar(otherflags);
}

Je pense que ça va, parce que nous identifions quand même des enums et des ints (non fortement typés).

Juste comme une note (plus longue), si vous

  • vouloir utiliser des énumérations fortement typées et
  • pas besoin de jouer avec vos drapeaux
  • la performance n'est pas un problème

Je viendrais avec ceci:

#include <set>

enum class EFoobarFlags
{
    FB_A = 1,
    FB_B,
    FB_C,
};

void Foobar(const std::set<EFoobarFlags>& flags)
{
    if (flags.find(EFoobarFlags::FB_A) != flags.end())
        // do sth
        ;
    if (flags.find(EFoobarFlags::FB_B) != flags.end())
        // do sth
        ;
}

void ExampleUsage()
{
    Foobar({EFoobarFlags::FB_A, EFoobarFlags::FB_B});
    std::set<EFoobarFlags> otherflags{};
    otherflags.insert(EFoobarFlags::FB_B);
    otherflags.erase(EFoobarFlags::FB_B);
    Foobar(otherflags);
}

en utilisant les listes d’initialisation C++ 11 et enum class.

2
yau

Comme ci-dessus (Kai) ou procédez comme suit. Vraiment les énumérations sont des "énumérations", ce que vous voulez faire est d'avoir un ensemble, donc vous devriez vraiment utiliser stl :: set

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

int main(void)
{
    AnimalFlags seahawk;
    //seahawk= CanFly | EatsFish | Endangered;
    seahawk= static_cast<AnimalFlags>(CanFly | EatsFish | Endangered);
}
2
Spacen Jasset

Peut-être comme NS_OPTIONS d'Objective-C.

#define ENUM(T1, T2) \
enum class T1 : T2; \
inline T1 operator~ (T1 a) { return (T1)~(int)a; } \
inline T1 operator| (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) | static_cast<T2>(b))); } \
inline T1 operator& (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) & static_cast<T2>(b))); } \
inline T1 operator^ (T1 a, T1 b) { return static_cast<T1>((static_cast<T2>(a) ^ static_cast<T2>(b))); } \
inline T1& operator|= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) |= static_cast<T2>(b))); } \
inline T1& operator&= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) &= static_cast<T2>(b))); } \
inline T1& operator^= (T1& a, T1 b) { return reinterpret_cast<T1&>((reinterpret_cast<T2&>(a) ^= static_cast<T2>(b))); } \
enum class T1 : T2

ENUM(Options, short) {
    FIRST  = 1 << 0,
    SECOND = 1 << 1,
    THIRD  = 1 << 2,
    FOURTH = 1 << 3
};

auto options = Options::FIRST | Options::SECOND;
options |= Options::THIRD;
if ((options & Options::SECOND) == Options::SECOND)
    cout << "Contains second option." << endl;
if ((options & Options::THIRD) == Options::THIRD)
    cout << "Contains third option." << endl;
return 0;

// Output:
// Contains second option. 
// Contains third option.
1
LunarEclipse

Seul le sucre syntaxique. Pas de métadonnées supplémentaires.

namespace UserRole // grupy
{ 
    constexpr uint8_t dea = 1;
    constexpr uint8_t red = 2;
    constexpr uint8_t stu = 4;
    constexpr uint8_t kie = 8;
    constexpr uint8_t adm = 16;
    constexpr uint8_t mas = 32;
}

Les opérateurs de drapeau sur type intégral ne fonctionnent que.

0
vSzemkel