Traktowanie enum
s 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
/ enum
konwersji. 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.flags
jako int
. Chciałbym jednak mieć mechanizm wymuszający bezpieczeństwo typu, aby ktoś nie mógł pisać seahawk.flags = HasMaximizeButton
.
[Flags]
atrybut działa dobrze, tj .:[Flags] enum class FlagBits{ Ready = 1, ReadMode = 2, WriteMode = 4, EOF = 8, Disabled = 16};
Odpowiedzi:
„Prawidłowym” sposobem jest zdefiniowanie operatorów bitowych dla wyliczenia, ponieważ:
Itd. Reszta operatorów bitów. Zmodyfikuj w razie potrzeby, jeśli zakres wyliczenia przekracza zakres int.
źródło
AnimalFlags
jest reprezentowany przez wyrażenieHasClaws | CanFly
? To nie to, coenum
ów są za. Użyj liczb całkowitych i stałych.(HasClaws | CanFly)
”.HasClaws
(= 1), jak iCanFly
(= 2). Jeśli zamiast tego po prostu przypiszesz wartości od 1 do 4 od razu i otrzymasz 3, może to być pojedynczyEatsFish
lub ponownie kombinacjaHasClaws
iCanFly
. 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.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.
Może utrzymywać wartości do int, czyli przez większość czasu 32 flagi, co jest wyraźnie odzwierciedlone w wielkości przesunięcia.
źródło
Dla leniwych ludzi takich jak ja, oto szablonowe rozwiązanie do kopiowania i wklejania:
źródło
using
tam, gdzie to stosowne, tak jakrel_ops
.Uwaga: jeśli pracujesz w środowisku Windows,
DEFINE_ENUM_FLAG_OPERATORS
w winnt.h zdefiniowane jest makro, które wykonuje zadanie za Ciebie. W takim przypadku możesz to zrobić:źródło
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 ...
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 ....
źródło
HasClaws | CanFly
jest jakiś rodzaj całkowitą, ale rodzajHasClaws
IsAnimalFlags
, a nie rodzaj całkowitą.max(|emin| − K, |emax|)
i równą(1u<<M) - 1
, gdzieM
jest nieujemną liczbą całkowitą. ”int
.enum
technicznie domyślnie nie jest domyślnymint
typem 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żint
gdyby 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żeint
”.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
: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 class
i używaconstexpr
do 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:
Możesz to zdefiniować podobnie jak zwykłe wyliczanie + typedef:
A użycie jest również podobne:
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, tjtypedef SafeEnum<enum TFlags_,uint8_t> TFlags;
.Zaznaczyłem
operator bool
zastąpienieexplicit
sł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
.źródło
std::underlying_type
zamiast 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.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:
źródło
Moim zdaniem żadna z dotychczasowych odpowiedzi nie jest idealna. Aby być idealnym, oczekiwałbym rozwiązania:
==
,!=
,=
,&
,&=
,|
,|=
i~
operatorzy w konwencjonalnym znaczeniu (tja & b
)if (a & b)...
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:
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 ). Jestunderlying_type_t
to 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;
źródło
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:
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:
UWAGA: Ta próbka nie jest kompletna, patrz sekcja „17.5.2.1.3 Typy maski bitowej”, aby uzyskać pełny zestaw operatorów.
źródło
Zadałem sobie to samo pytanie i wpadłem na ogólne rozwiązanie oparte na C ++ 11, podobne do soru:
Interfejs można ulepszyć do smaku. Następnie można go użyć w następujący sposób:
źródło
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:
źródło
Używam następującego makra:
Jest podobny do tych wymienionych powyżej, ale ma kilka ulepszeń:
int
)Musi zawierać Type_traits:
źródło
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 iexplicit
sł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ć
Następnie możesz uzyskać wartość surowych flag za pomocą
Oto kod.
źródło
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ł :)
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.
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.
źródło
Tylko cukier składniowy. Brak dodatkowych metadanych.
Operatory flag typu całkowego po prostu działają.
źródło
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
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
Możliwe użycie:
źródło
@Xaqq zapewnił naprawdę fajny, bezpieczny dla typu sposób użycia flag enum tutaj przez
flag_set
klasę.Kod opublikowałem w GitHub , użycie jest następujące:
źródło
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:
źródło
Oto moje rozwiązanie bez potrzeby przeciążania lub rzutowania:
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
Wymyśliłbym to:
używając list inicjalizacyjnych C ++ 11 i
enum class
.źródło
using Flags = unsigned long
wewnątrz przestrzeni nazw lub struktury zawierającej same wartości flag jako/*static*/ const Flags XY = 0x01
i tak dalej.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
źródło
Może jak NS_OPTIONS z Objective-C.
źródło