Często potrzeba razem kilku wyliczonych typów. Czasami zdarza się konflikt nazw. Przychodzą mi na myśl dwa rozwiązania: użyj przestrzeni nazw lub użyj „większych” nazw elementów wyliczenia. Mimo to rozwiązanie przestrzeni nazw ma dwie możliwe implementacje: klasę fikcyjną z zagnieżdżonym wyliczeniem lub pełną przestrzeń nazw.
Szukam zalet i wad wszystkich trzech podejść.
Przykład:
// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
switch (c) {
default: assert(false);
break; case cRed: //...
break; case cColorBlue: //...
//...
}
}
// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
switch (c) {
default: assert(false);
break; case Colors::cRed: //...
break; case Colors::cBlue: //...
//...
}
}
// a real namespace?
namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
switch (c) {
default: assert(false);
break; case Colors::cRed: //...
break; case Colors::cBlue: //...
//...
}
}
enum e {...}
mogą być anonimowe, tj.enum {...}
Co ma znacznie większy sens, gdy jest opakowane w przestrzeń nazw lub klasę.Odpowiedzi:
Oryginalna odpowiedź C ++ 03:
Korzyść z punktu A
namespace
(ponadclass
) jest to, że można użyćusing
deklaracji, kiedy chcesz.Problem z użyciem
namespace
jest to, że przestrzenie nazw można rozszerzyć gdzie indziej w kodzie. W dużym projekcie nie można mieć gwarancji, że dwie odrębne wyliczenia nie myślą, że są wywoływaneeFeelings
Aby uzyskać prostszy kod, używam a
struct
, ponieważ prawdopodobnie chcesz, aby zawartość była publiczna.Jeśli wykonujesz którąkolwiek z tych praktyk, wyprzedzasz konkurencję i prawdopodobnie nie musisz tego dalej analizować.
Nowsze porady dotyczące języka C ++ 11:
Jeśli używasz C ++ 11 lub nowszego,
enum class
niejawnie zakres wartości wyliczenia w nazwie wyliczenia.Dzięki
enum class
temu utracisz niejawne konwersje i porównania z typami całkowitymi, ale w praktyce może to pomóc w odkryciu niejednoznacznego lub błędnego kodu.źródło
FYI W C ++ 0x jest nowa składnia dla przypadków takich jak to, o czym wspomniałeś (patrz strona wiki C ++ 0x )
źródło
Powyższe odpowiedzi zhybrydyzowałem z czymś takim: (EDYCJA: jest to przydatne tylko dla wersji przed C ++ 11. Jeśli używasz C ++ 11, użyj
enum class
)Mam jeden duży plik nagłówkowy, który zawiera wszystkie wyliczenia mojego projektu, ponieważ te wyliczenia są współdzielone między klasami roboczymi i nie ma sensu umieszczać wyliczeń w samych klasach roboczych.
struct
Unika publicznego: cukier syntaktyczny itypedef
pozwala właściwie zadeklarować zmienne te teksty stałe wewnątrz innych klas robotniczych.Myślę, że używanie przestrzeni nazw w ogóle nie pomaga. Może to dlatego, że jestem programista C #, i tam mają korzystać z nazwy typu enum, odnosząc wartości, więc jestem do tego przyzwyczajony.
...
źródło
Zdecydowanie unikałbym używania do tego klasy; zamiast tego użyj przestrzeni nazw. Pytanie sprowadza się do tego, czy użyć przestrzeni nazw, czy użyć unikalnych identyfikatorów dla wartości wyliczeniowych. Osobiście użyłbym przestrzeni nazw, aby moje identyfikatory były krótsze i, miejmy nadzieję, bardziej zrozumiałe. Następnie kod aplikacji mógłby użyć dyrektywy „using namespace” i uczynić wszystko bardziej czytelnym.
Z powyższego przykładu:
źródło
Colors someColor = Red;
, ponieważ przestrzeń nazw nie stanowi typu. Zamiast tego musiałbyś pisaćColors::e someColor = Red;
, co jest dość sprzeczne z intuicją.Colors::e someColor
nawet z astruct/class
, gdybyś chciał użyć go wswitch
oświadczeniu? Jeśli używasz anonimowego,enum
przełącznik nie byłby w stanie ocenić plikustruct
.const e c
wydaje mi się mało czytelny :-) Nie rób tego. Jednak użycie przestrzeni nazw jest w porządku.Różnica między używaniem klasy lub przestrzeni nazw polega na tym, że klasy nie można ponownie otworzyć, tak jak w przypadku przestrzeni nazw. Pozwala to uniknąć możliwości nadużywania przestrzeni nazw w przyszłości, ale istnieje również problem, którego nie można dodać do zestawu wyliczeń.
Możliwą korzyścią z używania klasy jest to, że można ich używać jako argumentów typu szablonu, co nie ma miejsca w przypadku przestrzeni nazw:
Osobiście nie jestem fanem używania i wolę w pełni kwalifikowane nazwy, więc nie uważam tego za plus dla przestrzeni nazw. Jednak prawdopodobnie nie jest to najważniejsza decyzja, jaką podejmiesz w swoim projekcie!
źródło
Zaletą korzystania z klasy jest to, że można na niej zbudować pełnoprawną klasę.
Jak pokazuje powyższy przykład, używając klasy możesz:
Zwróć uwagę, że musisz zadeklarować
operator enum_type()
, aby C ++ wiedział, jak przekonwertować twoją klasę na podstawowe wyliczenie. W przeciwnym razie nie będzie można przekazać typu doswitch
instrukcji.źródło
Ponieważ wyliczenia są ograniczone do otaczającego zakresu, prawdopodobnie najlepiej jest je opakować w coś, aby uniknąć zanieczyszczenia globalnej przestrzeni nazw i uniknąć kolizji nazw. Wolę przestrzeń nazw od klasy po prostu dlatego, że
namespace
czuję się jak worek do przechowywania, podczas gdyclass
czuje się jak solidny obiekt (por. Debatastruct
vs.class
debata). Możliwą korzyścią dla przestrzeni nazw jest to, że można ją później rozszerzyć - przydatne, jeśli masz do czynienia z kodem innej firmy, którego nie możesz modyfikować.To wszystko jest oczywiście dyskusyjne, gdy otrzymujemy klasy enum w C ++ 0x.
źródło
Mam też tendencję do owijania wyliczeń na zajęcia.
Jak zasygnalizował Richard Corden, zaletą klasy jest to, że jest to typ w sensie c ++, więc można go używać z szablonami.
Mam specjalny zestaw narzędzi :: Enum dla moich potrzeb, który specjalizuję się w każdym szablonie, który zapewnia podstawowe funkcje (głównie: mapowanie wartości wyliczenia na std :: string, aby I / O były łatwiejsze do odczytania).
Mój mały szablon ma również tę dodatkową zaletę, że naprawdę sprawdza dozwolone wartości. Kompilator jest trochę luźny, jeśli chodzi o sprawdzanie, czy wartość naprawdę znajduje się w wyliczeniu:
Zawsze przeszkadzało mi, że kompilator tego nie wychwyci, ponieważ pozostaje wartość wyliczenia, która nie ma sensu (i której się nie spodziewasz).
Podobnie:
Ponownie main zwróci błąd.
Problem polega na tym, że kompilator dopasuje wyliczenie do najmniejszej dostępnej reprezentacji (tutaj potrzebujemy 2 bitów) i że wszystko, co pasuje do tej reprezentacji, jest uważane za poprawną wartość.
Istnieje również problem polegający na tym, że czasami wolisz mieć pętlę na możliwych wartościach zamiast przełącznika, aby nie trzeba było modyfikować wszystkich przełączników za każdym razem, gdy dodajesz wartość do wyliczenia.
Podsumowując, mój mały pomocnik naprawdę ułatwia pracę moim wyliczeniom (oczywiście dodaje trochę narzutów) i jest to możliwe tylko dlatego, że zagnieżdżam każdą wyliczenie w jego własnej strukturze :)
źródło