Kiedy ktoś twierdzi, że jakaś konstrukcja programistyczna jest „zła”, stara się zniechęcić cię do samodzielnego myślenia.
Pete Becker,
3
@NicolBolas: To jest raczej pytanie retoryczne, które ma dostarczyć odpowiedzi na najczęściej zadawane pytania (czy to naprawdę Często zadawane pytania to inna historia).
David Rodríguez - dribeas
@David, trwa dyskusja, czy powinien to być FAQ, czy nie, która zaczyna się tutaj . Wprowadź powitanie.
sbi
17
@PeteBecker Czasami starają się tylko chronić przed tobą.
piccy
geeksforgeeks.org/... Jest to również miejsce, aby zrozumieć dobry enumvs enum class.
mr_azad
Odpowiedzi:
472
C ++ ma dwa rodzaje enum:
enum classes
Zwykły enums
Oto kilka przykładów, jak je zadeklarować:
enumclassColor{ red, green, blue };// enum classenumAnimal{ dog, cat, bird, human };// plain enum
Jaka jest różnica między dwoma?
enum classes - nazwy modułu wyliczającego są lokalne dla wyliczenia, a ich wartości nie są domyślnie konwertowane na inne typy (jak inne enumlub int)
Zwykłe enums - gdzie nazwy modułu wyliczającego mają ten sam zakres co wyliczenie, a ich wartości domyślnie są konwertowane na liczby całkowite i inne typy
Przykład:
enumColor{ red, green, blue };// plain enum enumCard{ red_card, green_card, yellow_card };// another plain enum enumclassAnimal{ dog, deer, cat, bird, human };// enum classenumclassMammal{ kangaroo, deer, human };// another enum classvoid fun(){// examples of bad use of plain enums:Color color =Color::red;Card card =Card::green_card;int num = color;// no problemif(color ==Card::red_card)// no problem (bad)
cout <<"bad"<< endl;if(card ==Color::green)// no problem (bad)
cout <<"bad"<< endl;// examples of good use of enum classes (safe)Animal a =Animal::deer;Mammal m =Mammal::deer;int num2 = a;// errorif(m == a)// error (good)
cout <<"bad"<< endl;if(a ==Mammal::deer)// error (good)
cout <<"bad"<< endl;}
Wniosek:
enum classes powinny być preferowane, ponieważ powodują mniej niespodzianek, które mogą potencjalnie prowadzić do błędów.
Dobry przykład ... czy istnieje sposób na połączenie bezpieczeństwa typu wersji klasowej z promocją przestrzeni nazw wersji enum? To znaczy, jeśli mam klasę Aze stanem i tworzę enum class State { online, offline };jako dziecko klasy A, chciałbym zrobić w state == onlineśrodku kontrole Azamiast state == State::online... czy to możliwe?
zaznacz
31
Nie. Promocja przestrzeni nazw to Bad Thing ™, a połowę uzasadnienia enum classstanowiła jej eliminacja.
Puppy
10
W C ++ 11 możesz również używać jawnie wpisanych wyliczeń, takich jak enum Animal: unsigned int {pies, jeleń, kot, ptak}
Blasius Secundus
3
@Cat Plus Plus Rozumiem, że @Oleksiy mówi, że jest źle. Moje pytanie nie brzmiało, czy Oleksiy uważał, że jest złe. Moje pytanie było prośbą o wyszczególnienie, co jest w tym złego. W szczególności dlaczego na przykład Oleksiy uważa źle Color color = Color::red.
chux - Przywróć Monikę
9
@Cat Plus Plus Tak więc zły przykład nie pojawia się aż do if (color == Card::red_card)linii, 4 wiersze później niż komentarz (który widzę teraz dotyczy pierwszej połowy bloku). 2 wiersze bloku dają złe przykłady. Pierwsze 3 linie nie stanowią problemu. „Cały blok jest powodem, dla którego zwykłe wyliczenia są złe”, rzuciły mnie, ponieważ myślałem, że masz na myśli coś z nimi nie tak. Rozumiem teraz, to tylko konfiguracja. W każdym razie dzięki za opinie.
The enum class es ( „nowe teksty stałe”, „silne wyliczenia”) dotyczą trzech problemów z tradycyjnym C ++ wyliczenia:
konwencjonalne wyliczenia domyślnie przekształcają się w int, powodując błędy, gdy ktoś nie chce, aby wyliczenie działało jako liczba całkowita.
konwencjonalne wyliczenia eksportują swoje wyliczniki do otaczającego zakresu, powodując konflikty nazw.
enumnie można określić podstawowego typu an , powodując zamieszanie, problemy ze zgodnością i uniemożliwiając deklarację przesyłania dalej.
Nowe wyliczenia są „klasą wyliczania”, ponieważ łączą aspekty tradycyjnych wyliczeń (wartości nazw) z aspektami klas (członkowie o zasięgu i brak konwersji).
Tak więc, jak wspomnieli inni użytkownicy, „silne wyliczenia” uczynią kod bezpieczniejszym.
Podstawowym typem „klasycznego” enumjest typ liczb całkowitych wystarczająco duży, aby pasował do wszystkich wartości enum; to zwykle jest int. Również każdy wyliczony typ musi być zgodny zchar typem całkowitym znakiem lub bez znaku.
Jest to szeroki opis tego, jaki enummusi być typ bazowy, więc każdy kompilator sam podejmie decyzję o typie klasycznym, enuma czasem wynik może być zaskakujący.
Na przykład kilka razy widziałem taki kod:
enum E_MY_FAVOURITE_FRUITS
{
E_APPLE =0x01,
E_WATERMELON =0x02,
E_COCONUT =0x04,
E_STRAWBERRY =0x08,
E_CHERRY =0x10,
E_PINEAPPLE =0x20,
E_BANANA =0x40,
E_MANGO =0x80,
E_MY_FAVOURITE_FRUITS_FORCE8 =0xFF// 'Force' 8bits, how can you tell?};
W powyższym kodzie, niektórzy naiwni myśląc, że koder jest kompilator będzie przechowywać E_MY_FAVOURITE_FRUITSwartości do unsigned 8bit ... ale nie ma gwarancji o tym: kompilator może wybrać unsigned charlub intczy shortkażdy z tych typów są wystarczająco duże, aby zmieścić wszystkie wartości widoczne w enum. Dodanie pola E_MY_FAVOURITE_FRUITS_FORCE8jest dużym obciążeniem i nie zmusza kompilatora do dokonywania jakichkolwiek wyborów dotyczących podstawowego typu pliku enum.
Jeśli jest jakiś fragment kodu, który opiera się na rozmiarze typu i / lub zakłada, że E_MY_FAVOURITE_FRUITSbędzie miał pewną szerokość (np. Procedury serializacji), ten kod może zachowywać się w dziwny sposób w zależności od myśli kompilatora.
Co gorsza, jeśli jakiś kolega z pracy niedbale doda nową wartość do naszego enum:
E_DEVIL_FRUIT =0x100,// New fruit, with value greater than 8bits
Kompilator nie narzeka! Po prostu zmienia rozmiar, aby pasował do wszystkich wartości enum(przy założeniu, że kompilator używał najmniejszego możliwego typu, co jest założeniem, że nie możemy tego zrobić). Ten prosty i nieostrożny dodatek do enumsubtelności może zepsuć powiązany kod.
Ponieważ C ++ 11 jest możliwe, aby określić typ podstawowy dla ( enumi enum classdzięki rdb ), więc problem ten jest starannie rozwiązany:
Określając typ bazowy, jeśli pole ma wyrażenie spoza zakresu tego typu, kompilator będzie narzekał zamiast zmieniać typ bazowy.
Myślę, że to dobra poprawa bezpieczeństwa.
Dlaczego więc klasa enum jest lepsza od zwykłego enum? , jeśli możemy wybrać typ bazowy dla enum classwyliczeń scoped ( ) i unscoped ( enum), co jeszcze jest enum classlepszym wyborem ?:
Przypuszczam, że możemy ograniczyć typ bazowy wyliczenia również dla zwykłych wyliczeń, o ile mamy C ++ 11
Sagar Padhye
11
Przepraszamy, ale ta odpowiedź jest błędna. „klasa enum” nie ma nic wspólnego z możliwością określenia typu. Jest to niezależna funkcja, która istnieje zarówno dla regularnych wyliczeń, jak i dla klas wyliczania.
rdb
14
Taka jest umowa: * Klasy Enum są nową funkcją w C ++ 11. * Wyliczone typy są nową funkcją w C ++ 11. Są to dwie odrębne, niepowiązane nowe funkcje w C ++ 11. Możesz użyć obu, albo możesz użyć jednego lub drugiego.
rdb
2
Myślę, że Alex Allain zapewnia najpełniejsze proste wytłumaczenie ja widziałem jeszcze w tym blogu w [ cprogramming.com/c++11/... . Tradycyjne wyliczanie było dobre do używania nazw zamiast wartości całkowitych i unikania używania pre-procesora #defines, co było dobrą rzeczą - dodało przejrzystości. Klasa enum usuwa pojęcie wartości liczbowej modułu wyliczającego oraz wprowadza zakres i silne pisanie, które zwiększa się (cóż, może zwiększyć :-) poprawność programu. To przybliża cię o krok do myślenia o obiektach myślących.
Jon Spencer
2
Nawiasem mówiąc, zawsze jest zabawnie, kiedy przeglądasz kod i nagle dzieje się One Piece .
Justin Time - Przywróć Monikę
47
Podstawową zaletą używania klasy wyliczania w porównaniu do normalnych wyliczeń jest to, że możesz mieć te same zmienne wyliczające dla 2 różnych wyliczeń i nadal możesz je rozwiązywać (co OP określiło jako bezpieczne dla typu )
Na przykład:
enumclassColor1{ red, green, blue };//this will compileenumclassColor2{ red, green, blue };enumColor1{ red, green, blue };//this will not compile enumColor2{ red, green, blue };
Jeśli chodzi o podstawowe wyliczenia, kompilator nie będzie w stanie rozróżnić, czy redodnosi się do typu, Color1czy Color2jak w poniższej instrukcji.
enumColor1{ red, green, blue };enumColor2{ red, green, blue };int x = red;//Compile time error(which red are you refering to??)
@Oleksiy Ohh Nie przeczytałem twojego pytania poprawnie. Rozważ to dodatek dla tych, którzy nie wiedzieli.
Saksham,
w porządku! Prawie o tym zapomniałem
Oleksiy
oczywiście piszesz enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }, łatwo omijając problemy z przestrzenią nazw. Argument przestrzeni nazw jest jednym z trzech wymienionych tutaj, których w ogóle nie kupuję.
Jo So
2
@Jo So To rozwiązanie jest niepotrzebnym obejściem. ENUM: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }jest porównywalna do klasy ENUM: enum class Color1 { RED, GREEN, BLUE }. Dostęp jest podobny: COLOR1_REDvs Color1::RED, ale wersja Enum wymaga wpisania „COLOR1” w każdej wartości, co daje więcej miejsca na literówki, których unika zachowanie przestrzeni nazw klasy enum.
cdgraham
2
Prosimy o konstruktywną krytykę . Kiedy mówię więcej miejsca na literówki, mam na myśli, kiedy pierwotnie definiujesz wartości enum Color1, których kompilator nie może złapać, ponieważ prawdopodobnie nadal byłby to „poprawna” nazwa. Jeśli piszę RED, GREENi tak dalej, używając klasy wyliczeniowej, to nie może rozwiązać, enum Bananaponieważ wymaga podania Color1::REDw celu uzyskania dostępu do wartości (argument przestrzeni nazw). Nadal są dobre czasy, aby je wykorzystać enum, ale zachowanie przestrzeni nazw enum classczęsto może być bardzo korzystne.
cdgraham
20
Wyliczenia są używane do reprezentowania zestawu wartości całkowitych.
Słowo classkluczowe po enumokreśla, że wyliczenie jest silnie typowane, a jego wyliczacze mają zakres. W ten sposób enumklasy zapobiegają przypadkowemu niewłaściwemu użyciu stałych.
konwencjonalne wyliczenia domyślnie przekształcają się w int, powodując błędy, gdy ktoś nie chce, aby wyliczenie działało jako liczba całkowita.
enum color
{Red,Green,Yellow};enumclassNewColor{Red_1,Green_1,Yellow_1};int main(){//! Implicit conversion is possibleint i =Red;//! Need enum class name followed by access specifier. Ex: NewColor::Red_1int j =Red_1;// error C2065: 'Red_1': undeclared identifier//! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;int k =NewColor::Red_1;// error C2440: 'initializing': cannot convert from 'NewColor' to 'int'return0;}
konwencjonalne wyliczenia eksportują swoje wyliczniki do otaczającego zakresu, powodując konflikty nazw.
// Header.henum vehicle
{Car,Bus,Bike,Autorickshow};enumFourWheeler{Car,// error C2365: 'Car': redefinition; previous definition was 'enumerator'SmallBus};enumclassEditor{
vim,
eclipes,VisualStudio};enumclassCppEditor{
eclipes,// No error of redefinitionsVisualStudio,// No error of redefinitionsQtCreator};
Nie można określić podstawowego rodzaju wyliczenia, co powoduje zamieszanie, problemy ze zgodnością i uniemożliwia deklarację przesyłania dalej.
C ++ 11 pozwala także na pisanie „nieklasowych” wyliczeń . Problemy z zanieczyszczeniem przestrzeni nazw itp. Nadal istnieją. Spójrz na odpowiednie odpowiedzi, które istniały na długo przed tym ...
user2864740
7
nie konwertuj domyślnie na int
może wybrać, który typ leży u podstaw
Przestrzeń nazw ENUM, aby uniknąć zanieczyszczenia
W porównaniu z normalną klasą można zadeklarować do przodu, ale nie ma metod
Warto zauważyć, oprócz tych innych odpowiedzi, że C ++ 20 rozwiązuje jeden z problemów, który enum classma: gadatliwość. Wyobrażając sobie hipotetyczną enum class, Color.
Ponieważ, jak powiedziano w innych odpowiedziach, wyliczanie klas nie jest domyślnie konwertowane na int / bool, pomaga to również uniknąć błędnego kodu, takiego jak:
enumMyEnum{Value1,Value2,};...if(var ==Value1||Value2)// Should be "var == Value2" no error/warning
Aby zakończyć mój poprzedni komentarz, zauważ, że gcc ma teraz ostrzeżenie o nazwie -Wint-in-bool-context, które wychwytuje dokładnie tego rodzaju błędy.
Arnaud,
0
Jednej rzeczy, o której nie wspomniano wprost - funkcja scope daje ci możliwość posiadania tej samej nazwy dla metody wyliczania i klasy. Na przykład:
classTest{public:// these call ProcessCommand() internallyvoidTakeSnapshot();voidRestoreSnapshot();private:enumclassCommand// wouldn't be possible without 'class'{TakeSnapshot,RestoreSnapshot};voidProcessCommand(Command cmd);// signal the other thread or whatever};
enum
vsenum class
.Odpowiedzi:
C ++ ma dwa rodzaje
enum
:enum class
esenum
sOto kilka przykładów, jak je zadeklarować:
Jaka jest różnica między dwoma?
enum class
es - nazwy modułu wyliczającego są lokalne dla wyliczenia, a ich wartości nie są domyślnie konwertowane na inne typy (jak inneenum
lubint
)Zwykłe
enum
s - gdzie nazwy modułu wyliczającego mają ten sam zakres co wyliczenie, a ich wartości domyślnie są konwertowane na liczby całkowite i inne typyPrzykład:
Wniosek:
enum class
es powinny być preferowane, ponieważ powodują mniej niespodzianek, które mogą potencjalnie prowadzić do błędów.źródło
A
ze stanem i tworzęenum class State { online, offline };
jako dziecko klasyA
, chciałbym zrobić wstate == online
środku kontroleA
zamiaststate == State::online
... czy to możliwe?enum class
stanowiła jej eliminacja.Color color = Color::red
.if (color == Card::red_card)
linii, 4 wiersze później niż komentarz (który widzę teraz dotyczy pierwszej połowy bloku). 2 wiersze bloku dają złe przykłady. Pierwsze 3 linie nie stanowią problemu. „Cały blok jest powodem, dla którego zwykłe wyliczenia są złe”, rzuciły mnie, ponieważ myślałem, że masz na myśli coś z nimi nie tak. Rozumiem teraz, to tylko konfiguracja. W każdym razie dzięki za opinie.Z FAQ C ++ 11 Bjarne Stroustrup :
Tak więc, jak wspomnieli inni użytkownicy, „silne wyliczenia” uczynią kod bezpieczniejszym.
Podstawowym typem „klasycznego”
enum
jest typ liczb całkowitych wystarczająco duży, aby pasował do wszystkich wartościenum
; to zwykle jestint
. Również każdy wyliczony typ musi być zgodny zchar
typem całkowitym znakiem lub bez znaku.Jest to szeroki opis tego, jaki
enum
musi być typ bazowy, więc każdy kompilator sam podejmie decyzję o typie klasycznym,enum
a czasem wynik może być zaskakujący.Na przykład kilka razy widziałem taki kod:
W powyższym kodzie, niektórzy naiwni myśląc, że koder jest kompilator będzie przechowywać
E_MY_FAVOURITE_FRUITS
wartości do unsigned 8bit ... ale nie ma gwarancji o tym: kompilator może wybraćunsigned char
lubint
czyshort
każdy z tych typów są wystarczająco duże, aby zmieścić wszystkie wartości widoczne wenum
. Dodanie polaE_MY_FAVOURITE_FRUITS_FORCE8
jest dużym obciążeniem i nie zmusza kompilatora do dokonywania jakichkolwiek wyborów dotyczących podstawowego typu plikuenum
.Jeśli jest jakiś fragment kodu, który opiera się na rozmiarze typu i / lub zakłada, że
E_MY_FAVOURITE_FRUITS
będzie miał pewną szerokość (np. Procedury serializacji), ten kod może zachowywać się w dziwny sposób w zależności od myśli kompilatora.Co gorsza, jeśli jakiś kolega z pracy niedbale doda nową wartość do naszego
enum
:Kompilator nie narzeka! Po prostu zmienia rozmiar, aby pasował do wszystkich wartości
enum
(przy założeniu, że kompilator używał najmniejszego możliwego typu, co jest założeniem, że nie możemy tego zrobić). Ten prosty i nieostrożny dodatek doenum
subtelności może zepsuć powiązany kod.Ponieważ C ++ 11 jest możliwe, aby określić typ podstawowy dla (
enum
ienum class
dzięki rdb ), więc problem ten jest starannie rozwiązany:Określając typ bazowy, jeśli pole ma wyrażenie spoza zakresu tego typu, kompilator będzie narzekał zamiast zmieniać typ bazowy.
Myślę, że to dobra poprawa bezpieczeństwa.
Dlaczego więc klasa enum jest lepsza od zwykłego enum? , jeśli możemy wybrać typ bazowy dla
enum class
wyliczeń scoped ( ) i unscoped (enum
), co jeszcze jestenum class
lepszym wyborem ?:int
.źródło
Podstawową zaletą używania klasy wyliczania w porównaniu do normalnych wyliczeń jest to, że możesz mieć te same zmienne wyliczające dla 2 różnych wyliczeń i nadal możesz je rozwiązywać (co OP określiło jako bezpieczne dla typu )
Na przykład:
Jeśli chodzi o podstawowe wyliczenia, kompilator nie będzie w stanie rozróżnić, czy
red
odnosi się do typu,Color1
czyColor2
jak w poniższej instrukcji.źródło
enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }
, łatwo omijając problemy z przestrzenią nazw. Argument przestrzeni nazw jest jednym z trzech wymienionych tutaj, których w ogóle nie kupuję.enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
jest porównywalna do klasy ENUM:enum class Color1 { RED, GREEN, BLUE }
. Dostęp jest podobny:COLOR1_RED
vsColor1::RED
, ale wersja Enum wymaga wpisania „COLOR1” w każdej wartości, co daje więcej miejsca na literówki, których unika zachowanie przestrzeni nazw klasy enum.enum Color1
, których kompilator nie może złapać, ponieważ prawdopodobnie nadal byłby to „poprawna” nazwa. Jeśli piszęRED
,GREEN
i tak dalej, używając klasy wyliczeniowej, to nie może rozwiązać,enum Banana
ponieważ wymaga podaniaColor1::RED
w celu uzyskania dostępu do wartości (argument przestrzeni nazw). Nadal są dobre czasy, aby je wykorzystaćenum
, ale zachowanie przestrzeni nazwenum class
często może być bardzo korzystne.Wyliczenia są używane do reprezentowania zestawu wartości całkowitych.
Słowo
class
kluczowe poenum
określa, że wyliczenie jest silnie typowane, a jego wyliczacze mają zakres. W ten sposóbenum
klasy zapobiegają przypadkowemu niewłaściwemu użyciu stałych.Na przykład:
Tutaj nie możemy mieszać wartości zwierząt i zwierząt domowych.
źródło
FAQ w C ++ 11 wymienia poniższe punkty:
konwencjonalne wyliczenia domyślnie przekształcają się w int, powodując błędy, gdy ktoś nie chce, aby wyliczenie działało jako liczba całkowita.
konwencjonalne wyliczenia eksportują swoje wyliczniki do otaczającego zakresu, powodując konflikty nazw.
Nie można określić podstawowego rodzaju wyliczenia, co powoduje zamieszanie, problemy ze zgodnością i uniemożliwia deklarację przesyłania dalej.
.
.
źródło
źródło
Warto zauważyć, oprócz tych innych odpowiedzi, że C ++ 20 rozwiązuje jeden z problemów, który
enum class
ma: gadatliwość. Wyobrażając sobie hipotetycznąenum class
,Color
.Jest to pełne określenie w porównaniu do zwykłego
enum
wariantu, w którym nazwy mają zasięg globalny i dlatego nie trzeba ich poprzedzaćColor::
.Jednak w C ++ 20 możemy
using enum
wprowadzić wszystkie nazwy w wyliczeniu do bieżącego zakresu, rozwiązując problem.Więc teraz nie ma powodu, aby nie używać
enum class
.źródło
Ponieważ, jak powiedziano w innych odpowiedziach, wyliczanie klas nie jest domyślnie konwertowane na int / bool, pomaga to również uniknąć błędnego kodu, takiego jak:
źródło
Jednej rzeczy, o której nie wspomniano wprost - funkcja scope daje ci możliwość posiadania tej samej nazwy dla metody wyliczania i klasy. Na przykład:
źródło