Come usare enum come flag in C ++?


187

Considerare enums come flag funziona bene in C # tramite l' [Flags]attributo, ma qual è il modo migliore per farlo in C ++?

Ad esempio, vorrei scrivere:

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

seahawk.flags = CanFly | EatsFish | Endangered;

Tuttavia, ottengo errori del compilatore relativi a int/ enumconversioni. Esiste un modo migliore per esprimere questo oltre al semplice casting? Preferibilmente, non voglio fare affidamento su costrutti di librerie di terze parti come boost o Qt.

EDIT: Come indicato nelle risposte, posso evitare l'errore del compilatore dichiarando seahawk.flagscome int. Tuttavia, mi piacerebbe avere un meccanismo per imporre la sicurezza dei tipi, quindi qualcuno non può scrivere seahawk.flags = HasMaximizeButton.


Per quanto ne so in Visual C ++ 2013 l' [Flags]attributo funziona bene, vale a dire:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, No, non funziona con C ++ (anche 2015). Intendevi C #?
Ajay

5
@rivanov, L'attributo [Flags] funziona solo con .Net Framework nella CLI C ++, il C ++ nativo non supporta tali attributi.
Zoltan Tirinda,

Risposte:


251

Il modo "corretto" è definire operatori bit per l'enum, come:

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

Ecc. Resto degli operatori bit. Modificare se necessario se l'intervallo enum supera l'intervallo int.


42
^ questo. L'unica domanda è come automatizzare / modellare le definizioni degli operatori in modo da non doverle definire costantemente ogni volta che aggiungi un nuovo enum.
eodabash,

10
Inoltre, il cast da un int arbitrario torna al tipo enum è valido, anche se il valore int non corrisponde a nessuno degli identificatori dell'enum?
Ingo Schalk-Schupp,

8
Questa è una totale assurdità. Quale membro di AnimalFlagsè rappresentato dall'espressione HasClaws | CanFly? Questo è non è quello che enums sono per. Usa numeri interi e costanti.
Corse di leggerezza in orbita

26
@LightnessRacesinOrbit: non è corretto. Il dominio di un tipo enum è il dominio del suo tipo sottostante - è solo che ad alcuni è stato dato un nome. E per rispondere alla tua domanda: il membro " (HasClaws | CanFly)".
Xeo

5
@MarcusJ: limitare i tuoi valori a potenze di 2 ti consente di usare i tuoi enum come bit-flag. Quindi se ottieni un 3 lo conosci entrambi HasClaws(= 1) e CanFly(= 2). Se invece assegni direttamente i valori da 1 a 4 e ottieni un 3, potrebbe essere un singolo EatsFish, o ancora una combinazione di HasClawse CanFly. Se la tua enumerazione indica solo stati esclusivi, allora i valori consecutivi vanno bene, ma una combinazione di flag richiede che i valori siano bit esclusivi.
Christian Severin,

122

Nota (anche un po 'fuori tema): un altro modo per creare flag unici può essere fatto usando un bit shift. Io stesso lo trovo più facile da leggere.

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

Può contenere valori fino a un int in modo che, nella maggior parte dei casi, siano 32 flag che si riflettono chiaramente nell'importo del turno.


2
Potresti cancellare l'ultima virgola (3,) e aggiungere due punti dopo} per rendere il codice facile da copiare e incollare? Grazie
Katu

4
Nessuna menzione di esadecimale? Bestemmia!
Pharap,

1
@Jamie, i cardinali iniziano sempre con 1, solo gli ordinali possono iniziare con 0 o 1, a seconda di chi stai parlando.
Michael,

2
@Michael, è vero! In un enum, di solito prenoti 0 per BLAH_NONE. :-) Grazie per aver strappato quel ricordo!
Jamie,

1
@Katu • la virgola superflua sull'enumerazione finale è consentita dallo standard. Non mi piace, ma so già cosa mi direbbe Stroustrup ... "Non ti piace? Beh, sentiti libero di creare la tua lingua. L'ho fatto."
Eljay,

55

Per le persone pigre come me, ecco la soluzione per copiare e incollare:

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

23
+1 La pigrizia è una delle tre grandi virtù di un programmatore: threevirtues.com
Pharap

10
Questa è una soluzione molto bella, basta fare attenzione che fornirà allegramente operazioni bit per bit per qualsiasi tipo. Sto usando qualcosa di simile, ma con l'aggiunta di tratti che identificano i tipi a cui voglio che si applichi combinato con un po 'di magia abilita.
Rai,

@Rai: puoi sempre metterlo in uno spazio dei nomi e usingdove appropriato, proprio come rel_ops.
Yakov Galka,

1
@ybungalobill, ma avrai ancora lo stesso problema con le operazioni che si applicano a qualsiasi tipo nell'ambito di utilizzo, che presumibilmente corrisponderebbe all'enum? Penso che i tratti siano molto probabilmente necessari.
Rai,

19
Non usare questo codice. Apre la porta a QUALSIASI classe per essere gestita per errore. Anche il codice utilizza un cast vecchio stile che non passerà attraverso la compilation rigorosa di GCC shitalshah.com/p/… .
Shital Shah,

44

Nota se stai lavorando in ambiente Windows, c'è una DEFINE_ENUM_FLAG_OPERATORSmacro definita in winnt.h che fa il lavoro per te. Quindi, in questo caso, puoi farlo:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

Di che tipo è la variabile seahawk.flags?

Nel C ++ standard, le enumerazioni non sono sicure per i tipi. Sono effettivamente numeri interi.

AnimalFlags NON dovrebbe essere il tipo di variabile. La tua variabile dovrebbe essere int e l'errore sparirà.

Non è necessario inserire valori esadecimali come suggerito da altre persone. Non fa differenza.

I valori enum sono di tipo int per impostazione predefinita. Quindi puoi sicuramente bitally O combinarli e metterli insieme e archiviare il risultato in un int.

Il tipo enum è un sottoinsieme limitato di int il cui valore è uno dei suoi valori enumerati. Quindi, quando crei un nuovo valore al di fuori di tale intervallo, non puoi assegnarlo senza eseguire il casting su una variabile del tuo tipo enum.

Puoi anche cambiare i tipi di valore enum, se lo desideri, ma non ha senso questa domanda.

EDIT: Il poster diceva che erano interessati alla sicurezza del tipo e non volevano un valore che non dovrebbe esistere all'interno del tipo int.

Ma non sarebbe sicuro digitare un valore al di fuori dell'intervallo di AnimalFlags all'interno di una variabile di tipo AnimalFlags.

Esiste un modo sicuro per verificare i valori fuori intervallo anche se all'interno del tipo 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
};

Quanto sopra non ti impedisce di mettere un flag non valido da un enum diverso che ha il valore 1,2,4, o comunque 8.

Se vuoi la sicurezza assoluta del tipo, puoi semplicemente creare uno std :: set e memorizzare ogni flag al suo interno. Non è efficiente nello spazio, ma è sicuro per i tipi e ti dà la stessa abilità di un bitflag int.

C ++ 0x note: enumerazioni fortemente tipizzate

In C ++ 0x puoi finalmente avere valori enum di tipo sicuro ....

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

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

4
I valori enum non sono numeri interi, ma si convertono molto facilmente in numeri interi. Il tipo di HasClaws | CanFlyè un tipo intero, ma il tipo HasClawsè AnimalFlags, non un tipo intero.
Karu,

1
Ah, ma cosa succederebbe se definissimo l'intervallo corretto dell'enum in modo che non sia solo i valori dei singoli flag ma anche le loro combinazioni bit a bit. Quindi la risposta di eidolon è corretta e sostiene che solo le combinazioni dell'enum di bandiera corretto possono essere passate come quel tipo.
Scott,

3
@Scott: Vale la pena notare che lo standard C ++ definisce in questo modo l'intervallo di valori valido di un'istanza enum. "per un'enumerazione in cui emin è l'enumeratore più piccolo e emax è il più grande, i valori dell'enumerazione sono i valori nell'intervallo da bmin a bmax, definiti come segue: Sia K 1 per la rappresentazione del complemento a due e 0 per uno ' rappresentazione del complemento o della grandezza del segno. bmax è il valore più piccolo maggiore o uguale max(|emin| − K, |emax|)e uguale a (1u<<M) - 1, dove Mè un numero intero non negativo. "
Ben Voigt,

Per coloro che (come me) vogliono solo qualcosa di pratico che consenta di manipolare i valori dell'enum e che non sembri troppo brutto con modelli e tipi di casting, questa è una buona soluzione; basta definire le variabili da digitare int.
Eric Sokolowsky,

Si noti inoltre che in C ++, il normale enumnon è tecnicamente predefinito intcome tipo sottostante (pre-C ++ 11 (IIRC) o post-C ++ 11 quando non viene specificato alcun tipo sottostante), anche se lo enum class fa . Al contrario, il tipo sottostante viene impostato automaticamente su qualcosa di abbastanza grande da rappresentare tutti gli enumeratori, con l'unica vera e propria regola dura che è solo più grande di intse deve essere esplicitamente . Fondamentalmente, il tipo sottostante è specificato come (parafrasato) "qualunque cosa funzioni, ma è probabilmente a int meno che gli enumeratori non siano troppo grandi per int".
Justin Time - Ripristina Monica il

26

Trovo la risposta attualmente accettata da eidolon troppo pericolosa. L'ottimizzatore del compilatore potrebbe fare ipotesi sui possibili valori nell'enum e potresti recuperare la spazzatura con valori non validi. E di solito nessuno vuole definire tutte le possibili permutazioni nelle enumerazioni delle bandiere.

Come Brian R. Bondy afferma di seguito, se stai usando C ++ 11 (che tutti dovrebbero, è così buono) ora puoi farlo più facilmente con 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. 

Ciò garantisce una dimensione e un intervallo di valori stabili specificando un tipo per l'enum, inibisce il downcasting automatico degli enum in ints ecc. Utilizzando enum classe utilizza constexprper garantire che il codice per gli operatori venga incorporato e quindi veloce come i numeri normali.

Per le persone bloccate con dialetti C ++ pre-11

Se fossi bloccato con un compilatore che non supporta C ++ 11, andrei con il wrapping di un tipo-int in una classe che quindi consenta solo l'uso di operatori bit a bit e i tipi di quell'enum per impostare i suoi valori:

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

Puoi definirlo praticamente come un normale enum + typedef:

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

typedef SafeEnum<enum TFlags_>  TFlags;

E anche l'uso è simile:

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;

E puoi anche sovrascrivere il tipo sottostante per enumerazioni binarie stabili (come C ++ 11 enum foo : type) usando il secondo parametro template, cioè typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Ho contrassegnato l' operator booloverride con la explicitparola chiave di C ++ 11 per evitare che si traducesse in conversioni int, poiché quelle potrebbero causare il collasso di insiemi di flag in 0 o 1 durante la scrittura. Se non puoi usare C ++ 11, lascia fuori quel sovraccarico e riscrivi il primo condizionale nell'esempio come use as (myFlags & EFlagTwo) == EFlagTwo.


Come nota, consiglierei che l'operatore di esempio definito all'inizio utilizzi std::underlying_typeinvece di codificare un tipo specifico, o che il tipo sottostante sia fornito e usato come alias di tipo anziché direttamente. In questo modo, le modifiche al tipo sottostante si propagheranno automaticamente, invece di dover essere eseguite manualmente.
Justin Time - Ripristina Monica il

17

Il modo più semplice per farlo come mostrato qui , usando il bitset di classe della libreria standard .

Per emulare la funzione C # in modo sicuro, dovresti scrivere un wrapper di modello attorno al set di bit, sostituendo gli argomenti int con un enum dato come parametro di tipo al modello. Qualcosa di simile a:

    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;

4
Guarda questo per un codice più completo: codereview.stackexchange.com/questions/96146/…
Shital Shah,

11

Secondo me nessuna delle risposte finora è l'ideale. Per essere ideale mi aspetterei la soluzione:

  1. Sostenere il ==, !=, =, &, &=, |, |=e ~operatori senso convenzionale (cioè a & b)
  2. Essere sicuri dei tipi, ovvero non consentire l'assegnazione di valori non elencati come valori letterali o di numeri interi (tranne che per le combinazioni bit a bit di valori enumerati) o consentire l'assegnazione di una variabile enum a un tipo intero
  3. Espressioni di permesso come if (a & b)...
  4. Non richiedono macro malvagie, funzionalità specifiche di implementazione o altri hack

La maggior parte delle soluzioni finora ricade sui punti 2 o 3. WebDancer è la chiusura secondo me, ma fallisce al punto 3 e deve essere ripetuta per ogni enum.

La mia soluzione proposta è una versione generalizzata di WebDancer che affronta anche il punto 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;
}

Ciò crea sovraccarichi degli operatori necessari ma utilizza SFINAE per limitarli ai tipi elencati. Si noti che nell'interesse della brevità non ho definito tutti gli operatori, ma l'unico che è diverso è il &. Gli operatori sono attualmente globali (vale a dire si applicano a tutti i tipi enumerati) ma questo potrebbe essere ridotto posizionando i sovraccarichi in uno spazio dei nomi (cosa faccio) o aggiungendo condizioni SFINAE aggiuntive (magari utilizzando particolari tipi sottostanti o alias di tipo appositamente creati ). La underlying_type_tè un C ++ 14 caratteristica ma sembra essere ben supportato ed è facile da emulare per C ++ 11 con un semplicetemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Mentre la soluzione proposta funziona alla grande, introduce anche questo modello per gli enum che non sono pensati per essere trattati come flag. Questo è probabilmente il motivo per l'uso di macro (malvagie) come DEFINE_ENUM_FLAG_OPERATORS di Microsoft.
WebDancer,

@WebDancer, ovviamente hai ragione, ma poi l'ho già detto nella mia risposta. Ho anche suggerito due modi per affrontare il problema: inserirlo in uno spazio dei nomi o utilizzare una condizione SFINAE più restrittiva.
Trevor,

Il mio punto è se non si crea uno spazio dei nomi molto ristretto (ad esempio lo spazio dei nomi AllMyFlagEnums) o si ha una condizione SFINAE che in qualche modo seleziona solo alcuni enumerazioni esatte il codice è rotto nella mia mente. Piuttosto che rischiare, copio e incollo un "modello testuale" in cui sostituisco solo il nome enum e talvolta le macro "malvagie". Vorrei che ci fosse un modo migliore.
WebDancer

In primo luogo, causerà un problema solo se altrove nel codice è necessario eseguire una delle operazioni che si intende arrestare, ad esempio assegnare un valore letterale, intero o un elemento da un altro enum. Altrimenti l'enum modificato si comporta come un enum regolare, ad esempio gli elementi non devono necessariamente essere poteri di due e le operazioni di assegnazione, confronto e bit a bit funzionano normalmente. Se devi davvero assegnare valori letterali o mixare enumerazioni puoi comunque esprimere esplicitamente, con l'ulteriore vantaggio che il tuo intento sarà più chiaro. Quindi è probabile che non sia necessario ridurre il campo di applicazione.
Trevor,

In secondo luogo, se anche se è necessario ridurre l'ambito, potrebbe non essere necessario restringere lo spazio dei nomi, anche se ciò dipenderà da ciò che si sta facendo. Se stai lavorando su una libreria, forse hai già il tuo codice che dipende dagli enum in uno spazio dei nomi, quindi il codice enum va nello stesso spazio dei nomi. Se si richiede il comportamento enum per una classe (forse si desidera utilizzare gli enum come argomenti del metodo o variabili membro della classe), inserire il codice enum nella classe per lo stesso effetto. La linea di fondo è che non è necessario avvolgere uno spazio dei nomi attorno solo agli enum, anche se è possibile.
Trevor,

8

Lo standard C ++ ne parla esplicitamente, vedere la sezione "Tipi di maschera di bit 17.5.2.1.3":

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

Dato questo "modello" ottieni:

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

E simile per gli altri operatori. Notare anche "constexpr", è necessario se si desidera che il compilatore sia in grado di eseguire il tempo di compilazione degli operatori.

Se si utilizza C ++ / CLI e si desidera assegnare ai membri enum delle classi ref, è necessario utilizzare invece i riferimenti di tracciamento:

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

NOTA: questo esempio non è completo, vedere la sezione "Tipi di maschera di bit 17.5.2.1.3" per un set completo di operatori.


6

Mi sono ritrovato a porre la stessa domanda e ho trovato una soluzione generica basata su C ++ 11, simile a quella di 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'interfaccia può essere migliorata a piacere. Quindi può essere utilizzato in questo modo:

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

2
Guarda questo per un codice migliore e più completo: codereview.stackexchange.com/questions/96146/…
Shital Shah,

5
Ad eccezione del mio uso di numeric_limits, il codice è quasi lo stesso. Immagino che sia un modo comune per avere una classe enum di tipo sicuro. Direi che usare numeric_limits è meglio che mettere un SENTINEL alla fine di ogni enum.
Omair,

1
È un enorme set di bit!
Razze di leggerezza in orbita

(potenzialmente ...)
Razze di leggerezza in orbita

5

Se il tuo compilatore non supporta ancora enumerazioni fortemente tipizzate, puoi dare un'occhiata al seguente articolo dal sorgente c ++:

Dall'abstract:

Questo articolo presenta una soluzione al problema di vincolare le operazioni di bit per
consentire solo quelle sicure e legittime e trasformare tutte le manipolazioni di bit non valide in errori di compilazione. Soprattutto, la sintassi delle operazioni sui bit rimane invariata e il codice che funziona con i bit non deve essere modificato, tranne forse per correggere errori che non erano ancora stati rilevati.


5

Uso la seguente macro:

#define ENUM_FLAG_OPERATORS(T)                                                                                                                                            \
    inline T operator~ (T a) { return static_cast<T>( ~static_cast<std::underlying_type<T>::type>(a) ); }                                                                       \
    inline T operator| (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) | static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator& (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) & static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T operator^ (T a, T b) { return static_cast<T>( static_cast<std::underlying_type<T>::type>(a) ^ static_cast<std::underlying_type<T>::type>(b) ); }                   \
    inline T& operator|= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) |= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator&= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) &= static_cast<std::underlying_type<T>::type>(b) ); }   \
    inline T& operator^= (T& a, T b) { return reinterpret_cast<T&>( reinterpret_cast<std::underlying_type<T>::type&>(a) ^= static_cast<std::underlying_type<T>::type>(b) ); }

È simile a quelli sopra menzionati ma ha diversi miglioramenti:

  • È sicuro (non suppone che il tipo sottostante sia un int )
  • Non richiede di specificare manualmente il tipo sottostante (al contrario della risposta di @LunarEclipse)

Deve includere type_traits:

#include <type_traits>

4

Mi piacerebbe approfondire la risposta di Uliwitness , correggere il suo codice per C ++ 98 e usare il linguaggio Safe Bool , per mancanza del std::underlying_type<>template e delexplicit parola chiave nelle versioni C ++ sotto C ++ 11.

L'ho anche modificato in modo che i valori di enum possano essere sequenziali senza alcuna assegnazione esplicita, quindi puoi avere

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

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

È quindi possibile ottenere il valore dei flag non elaborati con

seahawk.flags.value();

Ecco il codice.

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

Ecco un'opzione per le maschere di bit se in realtà non hai un uso per i singoli valori enum (es. Non è necessario disattivarli) ... e se non sei preoccupato di mantenere la compatibilità binaria, ad esempio: tu non importa dove vivono i tuoi pezzi ... che probabilmente sei. Inoltre, è meglio non preoccuparsi troppo di scoping e controllo degli accessi. Hmmm, gli enum hanno delle belle proprietà per i bit-field ... mi chiedo se qualcuno lo abbia mai provato :)

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 :(";
    }
}

Possiamo vedere che la vita è fantastica, abbiamo i nostri valori discreti e abbiamo una buona int per & e | al contenuto dei nostri cuori, che ha ancora il contesto di ciò che significano i suoi bit. Tutto è coerente e prevedibile ... per me ... finché continuo a utilizzare il compilatore VC ++ di Microsoft con l'aggiornamento 3 su Win10 x64 e non tocco i miei flag di compilatore :)

Anche se tutto è fantastico ... abbiamo un certo contesto sul significato delle bandiere ora, dal momento che è in un'unione con il campo di battaglia nel terribile mondo reale in cui il tuo programma potrebbe essere responsabile di più di un singolo compito discreto che potresti ancora accidentalmente (abbastanza facilmente) rompono insieme due campi di bandiere di diversi sindacati (diciamo, AnimalProperties e ObjectProperties, poiché sono entrambi ints), mescolando tutti i tuoi bit, che è un orribile bug da rintracciare ... e come lo so molte persone in questo post non lavorano molto spesso con le maschere di bit, poiché costruirle è facile e mantenerle è 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 :(
}

Quindi rendi la tua dichiarazione sindacale privata per impedire l'accesso diretto alle "Bandiere", e devi aggiungere getter / setter e sovraccarichi dell'operatore, quindi creare una macro per tutto ciò e sei praticamente tornato da dove hai iniziato quando hai provato a fallo con un Enum.

Sfortunatamente se vuoi che il tuo codice sia portatile, non penso che ci sia modo di A) garantire il layout dei bit o B) determinare il layout dei bit al momento della compilazione (in modo da poterlo tracciare e almeno correggere per le modifiche in versioni / piattaforme ecc.) Offset in una struttura con campi di bit

In fase di esecuzione puoi giocare brutti scherzi con l'impostazione dei campi e XORing le bandiere per vedere quali bit sono cambiati, mi sembra piuttosto schifoso anche se i versi hanno una soluzione coerente al 100%, indipendente dalla piattaforma e completamente deterministica, ovvero: un ENUM.

TL; DR: non ascoltare chi odia. C ++ non è inglese. Solo perché la definizione letterale di una parola chiave abbreviata ereditata da C potrebbe non adattarsi al tuo utilizzo non significa che non dovresti usarla quando C e definizione C ++ della parola chiave include assolutamente il tuo caso d'uso. Puoi anche usare le strutture per modellare cose diverse dalle strutture e classi per cose diverse dalla scuola e dalla casta sociale. È possibile utilizzare float per i valori che sono radicati. Puoi usare char per variabili che non sono né bruciate né una persona in un romanzo, in una commedia o in un film. Qualsiasi programmatore che va nel dizionario per determinare il significato di una parola chiave prima che la specifica della lingua sia ... beh, terrò la mia lingua lì.

Se vuoi che il tuo codice sia modellato sulla lingua parlata, ti conviene scrivere in Objective-C, che per inciso usa anche enumerazioni pesanti per bitfield.


3

Solo zucchero sintattico. Nessun metadata aggiuntivo.

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

Gli operatori di flag su tipo integrale funzionano e basta.


IMHO questa è la risposta migliore. Sintassi client semplice e chiara. Vorrei solo usare "const int" anziché "constexpr uint8_t", ma il concetto è lo stesso.
yoyo

(scusa, "constexpr int")
yoyo

3

Attualmente non esiste alcun supporto linguistico per i flag enum, le classi Meta potrebbero intrinsecamente aggiungere questa funzione se mai dovesse far parte dello standard c ++.

La mia soluzione sarebbe quella di creare funzioni di template istanziate solo enum aggiungendo il supporto per operazioni bitwise di tipo sicuro per la classe enum usando il suo tipo sottostante:

File: 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_

Per comodità e per ridurre gli errori, potresti voler racchiudere le operazioni dei bit flag per enumerazioni e numeri interi:

File: 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_

Possibile utilizzo:

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

3

@Xaqq ha fornito un modo veramente sicuro e sicuro per usare enum flags qui da aflag_set classe.

Ho pubblicato il codice in GitHub , l'utilizzo è il seguente:

#include "flag_set.hpp"

enum class AnimalFlags : uint8_t {
    HAS_CLAWS,
    CAN_FLY,
    EATS_FISH,
    ENDANGERED,
    _
};

int main()
{
    flag_set<AnimalFlags> seahawkFlags(AnimalFlags::HAS_CLAWS
                                       | AnimalFlags::EATS_FISH
                                       | AnimalFlags::ENDANGERED);

    if (seahawkFlags & AnimalFlags::ENDANGERED)
        cout << "Seahawk is endangered";
}

2

Stai confondendo oggetti e raccolte di oggetti. In particolare, stai confondendo flag binari con set di flag binari. Una soluzione adeguata sarebbe simile a questa:

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

};

2

Ecco la mia soluzione senza bisogno di sovraccarichi o 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);
}

Penso che sia ok, perché identifichiamo comunque enumerazioni (non fortemente tipizzate) e ints.

Proprio come una nota a lato (più lunga), se tu

  • voglio usare enumerazioni fortemente tipizzate e
  • non c'è bisogno di armeggiare pesantemente con le tue bandiere
  • le prestazioni non sono un problema

Vorrei venire con questo:

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

usando gli elenchi di inizializzatori C ++ 11 e enum class.


A proposito, preferirei non raccomandare affatto enum per le bandiere. Motivo semplice: le combinazioni di flag non sono più elementi dell'enum. Quindi questo sembra abbastanza inadatto. In alternativa, userei un using Flags = unsigned longdentro uno spazio dei nomi o una struttura contenente i valori flag stessi /*static*/ const Flags XY = 0x01e così via.
yau,

1

Come sopra (Kai) o procedi come segue. Le enumerazioni sono davvero "Enumerazioni", quello che vuoi fare è avere un set, quindi dovresti davvero usare 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);
}

1

Forse come NS_OPTIONS di 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.

Puoi spiegare perché la tua risposta è la migliore? Ci sono molte altre risposte che hanno risposto a questa domanda, quindi per favore includi alcune informazioni per differenziare le tue.
trevorp,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.