Jak używać wyliczeń jako flag w C ++?


187

Traktowanie enums jak flagi działa dobrze w C # za pomocą [Flags]atrybutu, ale jaki jest najlepszy sposób, aby to zrobić w C ++?

Na przykład chciałbym napisać:

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

seahawk.flags = CanFly | EatsFish | Endangered;

Jednak dostaję błędy kompilatora dotyczące int/ enumkonwersji. Czy istnieje lepszy sposób na wyrażenie tego niż zwykłe rzucanie na castingach? Najlepiej nie chcę polegać na konstrukcjach z bibliotek stron trzecich, takich jak boost lub Qt.

EDYCJA: Jak wskazano w odpowiedziach, mogę uniknąć błędu kompilatora, deklarując seahawk.flagsjako int. Chciałbym jednak mieć mechanizm wymuszający bezpieczeństwo typu, aby ktoś nie mógł pisać seahawk.flags = HasMaximizeButton.


O ile wiem w Visual C ++ 2013 [Flags]atrybut działa dobrze, tj .:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
rivanov

@rivanov, Nie, to nie działa z C ++ (również 2015). Miałeś na myśli C #?
Ajay,

5
@rivanov, Atrybut [Flagi] działa tylko z .NET Framework w C ++ CLI, natywny C ++ nie obsługuje takich atrybutów.
Zoltan Tirinda

Odpowiedzi:


250

„Prawidłowym” sposobem jest zdefiniowanie operatorów bitowych dla wyliczenia, ponieważ:

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

Itd. Reszta operatorów bitów. Zmodyfikuj w razie potrzeby, jeśli zakres wyliczenia przekracza zakres int.


42
^ to. Jedyne pytanie dotyczy sposobu zautomatyzowania / tworzenia szablonów definicji operatora, aby nie trzeba było ciągle ich definiować za każdym razem, gdy dodaje się nowe wyliczenie.
eodabash

10
Ponadto, czy rzutowanie z dowolnej liczby int z powrotem na typ wyliczenia jest ważne, nawet jeśli wartość int nie odpowiada żadnemu identyfikatorowi wyliczenia?
Ingo Schalk-Schupp,

8
To kompletny nonsens. Który członek AnimalFlagsjest reprezentowany przez wyrażenie HasClaws | CanFly? To nie to, co enumów są za. Użyj liczb całkowitych i stałych.
Wyścigi lekkości na orbicie

26
@LightnessRacesinOrbit: To nie jest poprawne. Domena typu wyliczeniowego jest domeną typu bazowego - tylko niektóre z nich otrzymały nazwę. I aby odpowiedzieć na twoje pytanie: Członek „ (HasClaws | CanFly)”.
Xeo

5
@MarcusJ: ograniczenie wartości do potęg 2 oznacza, że ​​możesz używać enum jako flag bitowych. Zatem jeśli otrzymujesz 3, wiesz, że zarówno HasClaws(= 1), jak i CanFly(= 2). Jeśli zamiast tego po prostu przypiszesz wartości od 1 do 4 od razu i otrzymasz 3, może to być pojedynczy EatsFishlub ponownie kombinacja HasClawsi CanFly. Jeśli twoje wyliczenie oznacza tylko stany wyłączności, to kolejne wartości są w porządku, ale kombinacja flag wymaga, aby wartości były wykluczające bity.
Christian Severin,

122

Uwaga (również nieco nie na temat): Innym sposobem na stworzenie unikatowych flag może być zmiana bitów. Ja sam uważam to za łatwiejsze do odczytania.

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

Może utrzymywać wartości do int, czyli przez większość czasu 32 flagi, co jest wyraźnie odzwierciedlone w wielkości przesunięcia.


2
Czy możesz usunąć ostatni przecinek (3) i dodać dwukropek po}, aby ułatwić kopiowanie i wklejanie kodu? Dzięki
Katu,

4
Brak wzmianki o systemie szesnastkowym? Bluźnierstwo!
Pharap

1
@Jamie, kardynałowie zawsze zaczynają się od 1, tylko porządki mogą zaczynać się od 0 lub 1, w zależności od tego, z kim rozmawiasz.
Michael

2
@Michael, to prawda! W wyliczeniu zwykle rezerwujesz 0 na BLAH_NONE. :-) Dzięki za zgubienie tej pamięci!
Jamie

1
@Katu • zbędny przecinek w końcowym wyliczeniu jest dozwolony przez standard. Nie podoba mi się to, ale już wiem, co powiedziałby mi Stroustrup ... „Nie podoba ci się? Nie krępuj się tworzyć własnego języka.
Eljay

55

Dla leniwych ludzi takich jak ja, oto szablonowe rozwiązanie do kopiowania i wklejania:

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 Lenistwo jest jedną z trzech wielkich zalet programisty: threevirtues.com
Pharap

10
Jest to bardzo miłe rozwiązanie, należy jednak uważać, aby wesoło zapewniało operacje bitowe dla dowolnego typu. Używam czegoś podobnego, ale z dodatkiem cech identyfikujących typy, do których chcę to zastosować, w połączeniu z małą magią enable_if.
Rai

@Rai: zawsze możesz umieścić go w przestrzeni nazw i usingtam, gdzie to stosowne, tak jak rel_ops.
Jakow Galka

1
@ybungalobill, ale nadal będziesz mieć ten sam problem z operacjami dotyczącymi dowolnego typu w zakresie using, który prawdopodobnie pasowałby do wyliczenia? Myślę, że cechy są najprawdopodobniej konieczne.
Rai

19
Nie używaj tego kodu. To otwiera drzwi dla KAŻDEJ klasy, która może zostać użyta przez pomyłkę. Również kod używa starej obsady, która nie przejdzie przez ścisłą kompilację Gitals shitalshah.com/p/… .
Shital Shah

44

Uwaga: jeśli pracujesz w środowisku Windows, DEFINE_ENUM_FLAG_OPERATORSw winnt.h zdefiniowane jest makro, które wykonuje zadanie za Ciebie. W takim przypadku możesz to zrobić:

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

seahawk.flags = CanFly | EatsFish | Endangered;

44

Jakiego typu jest zmienna seahawk.flags?

W standardowym C ++ wyliczenia nie są bezpieczne dla typu. Są to w rzeczywistości liczby całkowite.

AnimalFlags NIE powinien być typem twojej zmiennej. Zmienna powinna mieć wartość int, a błąd zniknie.

Wprowadzanie wartości szesnastkowych, jak sugerują inne osoby, nie jest potrzebne. To nie robi różnicy.

Domyślnie wartości wyliczeniowe SĄ typu int. Możesz więc na pewno bitowo LUB połączyć je i złożyć je razem i zapisać wynik w postaci int.

Typ wyliczenia jest ograniczonym podzbiorem int, którego wartość jest jedną z jego wyliczonych wartości. Dlatego, gdy tworzysz nową wartość poza tym zakresem, nie możesz jej przypisać bez rzutowania na zmienną typu wyliczeniowego.

Możesz również zmienić typy wartości wyliczenia, jeśli chcesz, ale nie ma sensu to pytanie.

EDYCJA: Plakat powiedział, że martwią się bezpieczeństwem typu i nie chcą wartości, która nie powinna istnieć wewnątrz typu int.

Jednak umieszczenie wartości poza zakresem AnimalFlags w zmiennej typu AnimalFlags byłoby niebezpieczne.

Istnieje bezpieczny sposób sprawdzenia wartości poza zakresem, chociaż wewnątrz typu 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
};

Powyższe nie powstrzymuje cię przed umieszczeniem nieprawidłowej flagi z innego wyliczenia, które ma wartość 1,2,4 lub 8.

Jeśli chcesz absolutnego bezpieczeństwa typu, możesz po prostu stworzyć std :: set i przechowywać w nim każdą flagę. Nie zajmuje mało miejsca, ale jest bezpieczny dla typu i daje taką samą zdolność jak bitflag int.

Uwaga C ++ 0x: Wyrazy silnie wpisane

W C ++ 0x możesz wreszcie wpisać bezpieczne wartości wyliczeniowe ....

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

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

4
Wartości wyliczeniowe nie są liczbami całkowitymi, ale bardzo łatwo przekształcają się w liczby całkowite. Typ HasClaws | CanFlyjest jakiś rodzaj całkowitą, ale rodzaj HasClawsIs AnimalFlags, a nie rodzaj całkowitą.
Karu

1
Ach, ale co, jeśli zdefiniujemy poprawny zakres wyliczenia, aby nie były to tylko poszczególne wartości flagi, ale także ich kombinacje bitowe. Zatem odpowiedź eidolona jest poprawna i utrzymuje, że tylko kombinacje poprawnego wyliczenia flagi można przekazać jako ten typ.
Scott,

3
@Scott: Warto zauważyć, że standard C ++ definiuje w ten sposób prawidłowy zakres wartości wystąpienia enum. „dla wyliczenia, w którym emin jest najmniejszym wyliczaczem, a emax jest największym, wartości wyliczenia są wartościami z zakresu od bmin do bmax, zdefiniowanymi w następujący sposób: Niech K będzie 1 dla reprezentacji uzupełnienia dwóch i 0 dla jedności” dopełnienie lub reprezentacja wielkości znaku. bmax jest najmniejszą wartością większą lub równą max(|emin| − K, |emax|)i równą (1u<<M) - 1, gdzie Mjest nieujemną liczbą całkowitą. ”
Ben Voigt

Dla tych, którzy (jak ja) chcą po prostu czegoś praktycznego, który pozwala bitowo manipulować wartościami wyliczenia i nie wygląda zbyt brzydko w przypadku szablonów i rzutowania tekstu, jest to dobre rozwiązanie; po prostu zdefiniuj zmienne, które mają być typem int.
Eric Sokolowsky,

Zauważ też, że w C ++ normalnie enumtechnicznie domyślnie nie jest domyślnym inttypem bazowym (ani wcześniejszym niż C ++ 11 (IIRC), ani późniejszym niż C ++ 11, gdy nie określono żadnego typu bazowego), chociaż enum class tak jest . Zamiast bazowe domyślnie typ coś wystarczająco duży, aby reprezentować wszystkich rachmistrzów, z jedynym prawdziwym twardym zasadą, że to tylko większe niż intgdyby wyraźnie musi być. Zasadniczo typ bazowy jest określony jako (sparafrazowany) „cokolwiek działa, ale prawdopodobnie tak jest, chybaint że dla liczników są zbyt duże int”.
Justin Time - Przywróć Monikę

26

Uważam, że obecnie akceptowana przez eidolon odpowiedź jest zbyt niebezpieczna. Optymalizator kompilatora może przyjmować założenia o możliwych wartościach w wyliczeniu i możesz odzyskać śmieci z nieprawidłowymi wartościami. I zwykle nikt nie chce definiować wszystkich możliwych permutacji w wyliczeniach flag.

Jak stwierdza Brian R. Bondy poniżej, jeśli używasz C ++ 11 (co wszyscy powinni, to jest tak dobre), możesz teraz zrobić to łatwiej 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. 

Zapewnia to stabilny zakres wielkości i wartości, określając typ wyliczenia, hamuje automatyczne obniżanie wyliczeń do liczb wewnętrznych itp. Za pomocą enum classi używa constexprdo zapewnienia, że ​​kod operatorów zostanie wstawiony, a tym samym tak samo szybki jak zwykłe liczby.

Dla osób, które utknęły w dialektach C ++ sprzed 11 lat

Gdybym utknął w kompilatorze, który nie obsługuje C ++ 11, wybrałbym zawijanie typu int w klasie, która pozwala na użycie tylko operatorów bitowych i typów z tego wyliczenia do ustawienia jego wartości:

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

Możesz to zdefiniować podobnie jak zwykłe wyliczanie + typedef:

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

typedef SafeEnum<enum TFlags_>  TFlags;

A użycie jest również podobne:

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;

Możesz także zastąpić typ bazowy dla binarnie stabilnych wyliczeń (takich jak C ++ 11 enum foo : type), używając drugiego parametru szablonu, tj typedef SafeEnum<enum TFlags_,uint8_t> TFlags;.

Zaznaczyłem operator boolzastąpienie explicitsłowem kluczowym C ++ 11, aby zapobiec skutkom konwersji int, ponieważ mogą one spowodować, że zestawy flag zakończą się zapadaniem na 0 lub 1 podczas ich zapisywania. Jeśli nie możesz użyć C ++ 11, zostaw to przeciążenie na zewnątrz i przepisz pierwszy warunek w przykładzie użycia jako (myFlags & EFlagTwo) == EFlagTwo.


Jako notatkę zaleciłbym, aby przykładowy operator zdefiniowany na początku używał std::underlying_typezamiast na stałe kodować określony typ, lub aby typ bazowy był podawany i używany jako alias typu zamiast bezpośrednio. W ten sposób zmiany w typie bazowym będą propagowane automatycznie, bez konieczności ręcznego wprowadzania zmian.
Justin Time - Przywróć Monikę

17

Najłatwiejszym sposobem, aby to zrobić, jak pokazano tutaj , przy użyciu standardowego zestawu bitów klasy biblioteki .

Aby emulować funkcję C # w sposób bezpieczny dla typu, należy napisać opakowanie szablonu wokół zestawu bitów, zastępując argumenty int wyliczeniem podanym jako parametr typu do szablonu. Coś jak:

    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
Spójrz na to, aby uzyskać pełniejszy kod: codereview.stackexchange.com/questions/96146/…
Shital Shah

11

Moim zdaniem żadna z dotychczasowych odpowiedzi nie jest idealna. Aby być idealnym, oczekiwałbym rozwiązania:

  1. Wspierać ==, !=, =, &, &=, |, |=i ~operatorzy w konwencjonalnym znaczeniu (tj a & b)
  2. Bądź bezpieczny dla typu, tzn. Nie zezwalaj na przypisywanie wartości niepoliczonych, takich jak literały lub typy liczb całkowitych (z wyjątkiem bitowych kombinacji wartości liczbowych) lub zezwalaj na przypisywanie zmiennej wyliczeniowej do typu liczb całkowitych
  3. Zezwól na wyrażenia, takie jak if (a & b)...
  4. Nie wymagają złych makr, specyficznych dla implementacji funkcji ani innych hacków

Większość dotychczasowych rozwiązań dotyczy punktów 2 i 3. WebDancer jest moim zdaniem zakończeniem, ale zawodzi w punkcie 3 i musi być powtarzany dla każdego wyliczenia.

Moje zaproponowane rozwiązanie to uogólniona wersja WebDancera, która również odnosi się do punktu 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;
}

Powoduje to przeciążenie niezbędnych operatorów, ale używa SFINAE w celu ograniczenia ich do typów wyliczonych. Zauważ, że dla zachowania zwięzłości nie zdefiniowałem wszystkich operatorów, ale jedyny, który jest inny, to &. Operatory są obecnie globalne (tj. Mają zastosowanie do wszystkich typów wyliczanych), ale można to zmniejszyć, umieszczając przeciążenia w przestrzeni nazw (co robię), lub dodając dodatkowe warunki SFINAE (być może przy użyciu określonych typów podstawowych lub specjalnie utworzonych aliasów typów ). Jest underlying_type_tto funkcja C ++ 14, ale wydaje się być dobrze obsługiwana i jest łatwa do emulacji dla C ++ 11 za pomocą prostegotemplate<typename T> using underlying_type_t = underlying_type<T>::type;


Chociaż proponowane rozwiązanie działa świetnie, wprowadza również ten wzorzec dla wyliczeń, które nie powinny być traktowane jako flagi. Jest to prawdopodobnie powód używania (złych) makr, takich jak DEFINE_ENUM_FLAG_OPERATORS firmy Microsoft.
WebDancer

@WebDancer, oczywiście masz rację, ale powiedziałem to już w mojej odpowiedzi. Zasugerowałem również dwa sposoby rozwiązania problemu - umieszczenie go w przestrzeni nazw lub użycie bardziej restrykcyjnego warunku SFINAE.
Trevor,

Chodzi mi o to, chyba że stworzysz naprawdę wąską przestrzeń nazw (np. Przestrzeń nazw AllMyFlagEnums) lub nie masz warunku SFINAE, który w jakiś sposób wybiera tylko kilka dokładnych wyliczeń, kod jest uszkodzony w mojej głowie. Zamiast ryzykować, kopiuję i wklejam „szablon tekstowy”, w którym po prostu zastępuję, a następnie wyliczam nazwę, a czasem makra „zła”. Chciałbym, żeby był lepszy sposób.
WebDancer

Po pierwsze, spowoduje to problem tylko wtedy, gdy gdzie indziej w kodzie będziesz musiał zrobić jedną z rzeczy, które ma zatrzymać, np. Przypisać literał, liczbę całkowitą lub element z innego wyliczenia. W przeciwnym razie zmodyfikowany wyliczenie zachowuje się jak zwykły wyliczenie, np. Elementy niekoniecznie muszą być potęgami dwóch, a operacje przypisania, porównania i bitowe działają normalnie. Jeśli naprawdę musisz przypisać literały lub miksować wyliczenia, możesz nadal jawnie rzucać, z dodatkową zaletą, że twoje zamiary będą wyraźniejsze. Są więc szanse, że nie będzie potrzeby ograniczania zakresu.
Trevor

Po drugie, jeśli nawet musisz zmniejszyć zakres, przestrzeń nazw może nie wymagać wąskiego - choć będzie to zależeć od tego, co robisz. Jeśli pracujesz nad biblioteką, być może masz już kod zależny od wyliczeń w przestrzeni nazw, a następnie kod wyliczania po prostu idzie w tej samej przestrzeni nazw. Jeśli potrzebujesz zachowania wyliczania dla klasy (być może chcesz użyć wyliczeń jako argumentów metody lub zmiennych składowych klasy), umieść kod wyliczania w klasie dla tego samego efektu. Najważniejsze jest to, że nie musisz zawijać przestrzeni nazw tylko wyliczeniami - chociaż możesz.
Trevor

8

Standard C ++ wyraźnie o tym mówi, patrz sekcja „17.5.2.1.3 Typy maski bitowej”:

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

Biorąc pod uwagę ten „szablon” otrzymujesz:

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

I podobnie dla innych operatorów. Zwróć także uwagę na „constexpr”, jest potrzebny, jeśli chcesz, aby kompilator mógł wykonywać czas kompilacji operatorów.

Jeśli używasz C ++ / CLI i chcesz móc przypisywać do członków enum klas referencyjnych, musisz zamiast tego użyć referencji śledzących:

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

UWAGA: Ta próbka nie jest kompletna, patrz sekcja „17.5.2.1.3 Typy maski bitowej”, aby uzyskać pełny zestaw operatorów.


6

Zadałem sobie to samo pytanie i wpadłem na ogólne rozwiązanie oparte na C ++ 11, podobne do 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);
    }   
};

Interfejs można ulepszyć do smaku. Następnie można go użyć w następujący sposób:

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

2
Spójrz na to, aby uzyskać lepszy i bardziej kompletny kod: codereview.stackexchange.com/questions/96146/…
Shital Shah

5
Z wyjątkiem mojego użycia wartości liczbowych kod jest prawie taki sam. Myślę, że jest to powszechny sposób posiadania klasy enum bezpiecznej dla typu. Argumentowałbym, że użycie liczb_limits jest lepsze niż umieszczenie SENTINEL na końcu każdego wyliczenia.
Omair

1
To ogromny zestaw bitów!
Wyścigi lekkości na orbicie

(potencjalnie ...)
Lekkość ściga się na orbicie

5

Jeśli Twój kompilator nie obsługuje jeszcze wyliczeń silnie typowanych, możesz rzucić okiem na następujący artykuł ze źródła c ++:

Z streszczenia:

W tym artykule przedstawiono rozwiązanie problemu ograniczania operacji bitowych w celu
umożliwienia tylko bezpiecznych i legalnych operacji , a także przekształcania wszystkich nieprawidłowych operacji bitowych w błędy kompilacji. Co najlepsze, składnia operacji bitowych pozostaje niezmieniona, a kod pracujący z bitami nie musi być modyfikowany, z wyjątkiem ewentualnego naprawienia błędów, które do tej pory pozostały niewykryte.


5

Używam następującego makra:

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

Jest podobny do tych wymienionych powyżej, ale ma kilka ulepszeń:

  • Jest to rodzaj bezpieczny (nie przypuszczać, że typ bazowy jest int)
  • Nie wymaga ręcznego określania typu bazowego (w przeciwieństwie do odpowiedzi @LunarEclipse)

Musi zawierać Type_traits:

#include <type_traits>

4

Chciałbym rozwinąć odpowiedź na temat Uliwitness , naprawiając jego kod dla C ++ 98 i używając idiomu Safe Bool , z powodu braku std::underlying_type<>szablonu i explicitsłowa kluczowego w wersjach C ++ poniżej C ++ 11.

Zmodyfikowałem go również, aby wartości wyliczeniowe mogły być sekwencyjne bez wyraźnego przypisania, dzięki czemu można je mieć

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

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

Następnie możesz uzyskać wartość surowych flag za pomocą

seahawk.flags.value();

Oto kod.

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

Oto opcja dla masek bitowych, jeśli tak naprawdę nie masz zastosowania do poszczególnych wartości wyliczenia (np. Nie musisz ich wyłączać) ... i jeśli nie martwisz się o utrzymanie zgodności binarnej, tj .: nie obchodzi cię, gdzie mieszkają twoje bity ... którym prawdopodobnie jesteś. Lepiej też nie przejmuj się zasięgiem i kontrolą dostępu. Hmmm, wyliczenia mają jakieś fajne właściwości dla pól bitowych ... zastanawiam się, czy ktoś kiedykolwiek tego próbował :)

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

Widzimy, że życie jest wspaniałe, mamy nasze dyskretne wartości i mamy miłe int & & do naszych serc, które wciąż mają kontekst tego, co oznaczają jego bity. Wszystko jest spójne i przewidywalne ... dla mnie ... dopóki używam kompilatora Microsoft VC ++ w / Update 3 na Win10 x64 i nie dotykam flag mojego kompilatora :)

Mimo że wszystko jest świetne ... mamy teraz pewien kontekst co do znaczenia flag, ponieważ znajduje się on w związku z polem bitowym w strasznym realnym świecie, w którym twój program może być odpowiedzialny za więcej niż jedno dyskretne zadanie, które możesz wciąż przypadkowo (dość łatwo) rozbija dwa pola flag różnych związków (powiedzmy AnimalProperties i ObjectProperties, ponieważ oba są intami), mieszając wszystkie twoje bity, co jest strasznym błędem do wyśledzenia ... i skąd to wiem wiele osób na tym poście nie pracuje zbyt często z maskami bitowymi, ponieważ ich budowa jest łatwa, a utrzymanie ich trudne.

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

Więc wtedy ustawiasz swoją deklarację związkową jako prywatną, aby zapobiec bezpośredniemu dostępowi do „Flagi”, i musisz dodać pobierające / ustawiające i przeciążające operatorów, a następnie utworzyć makro dla tego wszystkiego, i jesteś w zasadzie tam, gdzie zacząłeś, kiedy próbowałeś zrób to z Enum.

Niestety, jeśli chcesz, aby Twój kod był przenośny, nie sądzę, że istnieje jakikolwiek sposób na A) zagwarantowanie układu bitów lub B) określenie układu bitów w czasie kompilacji (abyś mógł go śledzić i przynajmniej poprawiać zmiany w różnych wersje / platformy itp.) Przesunięcie w strukturze z polami bitowymi

W czasie wykonywania możesz grać w sztuczki z ustawieniem pól i XOR-owaniem flag, aby zobaczyć, które bity się zmieniły, brzmi to dla mnie dość głupio, chociaż wersety mają w 100% spójne, niezależne od platformy i całkowicie deterministyczne rozwiązanie, tj. ENUM.

TL; DR: Nie słuchajcie hejterów. C ++ nie jest językiem angielskim. To, że dosłowna definicja skróconego słowa kluczowego odziedziczonego po C może nie pasować do twojego zastosowania, nie oznacza, że ​​nie powinieneś go używać, gdy definicja słowa kluczowego C i C ++ bezwzględnie obejmuje przypadek użycia. Możesz także użyć struktur do modelowania rzeczy innych niż struktury i klas do rzeczy innych niż szkoła i kasta społeczna. Możesz użyć float dla wartości, które są uziemione. Możesz użyć char dla zmiennych, które nie są wypalone ani osoby w powieści, sztuce lub filmie. Każdy programista, który idzie do słownika, aby ustalić znaczenie słowa kluczowego przed specyfikacją języka, jest ... no cóż, trzymam tam język.

Jeśli chcesz modelować swój kod na podstawie języka mówionego, najlepiej pisz w Objective-C, który nawiasem mówiąc, często wykorzystuje wyliczenia dla pól bitowych.


3

Tylko cukier składniowy. Brak dodatkowych metadanych.

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

Operatory flag typu całkowego po prostu działają.


IMHO to najlepsza odpowiedź. Czysta i prosta, łatwa składnia klienta. Użyłbym po prostu „const int” zamiast „constexpr uint8_t”, ale koncepcja jest taka sama.
yoyo

(przepraszam, „constexpr int”)
yoyo

3

Obecnie nie ma obsługi języka dla flag enum, klasy Meta mogą z natury dodać tę funkcję, jeśli kiedykolwiek byłaby częścią standardu c ++.

Moim rozwiązaniem byłoby utworzenie funkcji szablonu tworzonych tylko w trybie wyliczania, dodając obsługę bezpiecznych operacji bitowych typu dla klasy wyliczeniowej przy użyciu jej podstawowego typu:

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

Dla wygody i dla zmniejszenia błędów możesz chcieć zawijać operacje na flagach bitowych także dla wyliczeń i liczb całkowitych:

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

Możliwe użycie:

#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 zapewnił naprawdę fajny, bezpieczny dla typu sposób użycia flag enum tutaj przez flag_setklasę.

Kod opublikowałem w GitHub , użycie jest następujące:

#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

Mylicie przedmioty i kolekcje przedmiotów. W szczególności mylisz flagi binarne z zestawami flag binarnych. Właściwe rozwiązanie wyglądałoby tak:

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

Oto moje rozwiązanie bez potrzeby przeciążania lub rzutowania:

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

Myślę, że to w porządku, ponieważ i tak identyfikujemy (niezbyt typowane) wyliczenia i inty.

Tak jak (dłuższa) notatka, jeśli ty

  • chcesz użyć silnie wpisanych wyliczeń i
  • nie potrzebuję ciężkiego majstrowania przy swoich flagach
  • wydajność nie stanowi problemu

Wymyśliłbym to:

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

używając list inicjalizacyjnych C ++ 11 i enum class.


Nawiasem mówiąc, raczej w ogóle nie polecałbym enumów do flag. Prosty powód: kombinacje flag nie są ponownie elementami wyliczenia. To wydaje się dość nieodpowiednie. Alternatywnie użyłbym using Flags = unsigned longwewnątrz przestrzeni nazw lub struktury zawierającej same wartości flag jako /*static*/ const Flags XY = 0x01i tak dalej.
yau

1

Jak wyżej (Kai) lub wykonaj następujące czynności. Naprawdę wyliczenia są „Wyliczeniami”, to, co chcesz zrobić, to mieć zestaw, dlatego naprawdę powinieneś używać 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

Może jak NS_OPTIONS z 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.

Czy możesz wyjaśnić, dlaczego Twoja odpowiedź jest najlepsza? Istnieje kilka innych odpowiedzi, które odpowiedziały na to pytanie, więc proszę podać informacje, które pozwolą je rozróżnić.
trevorp
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.