Większość programistów języka C++ zna doskonale typy wyliczeniowe (enum), zdefiniowane w standardzie C++98/03. Jest to wygodne, często wykorzystywane narzędzie. Jest to jednak również funkcjonalność, która posiada kilka zauważalnych niedociągnięć, przez co użycie typów wyliczeniowych nie jest tak przyjemne i odporne na błędy, jak mogłoby być.

W standardzie C++11 doczekaliśmy się pod tym względem malutkiej rewolucji – zdefiniowane zostały tzw. silne typy wyliczeniowe (ang. strongly-typed enums).

Silny typ wyliczeniowy jest definiowany parą słów kluczowych enum class, w ten sposób:

Jeśli chodzi o taką deklarację typu, jedyną różnicą pomiędzy “starym” a “nowym” jest słowo kluczowe class. Nowości jest jednak znacznie więcej…

Na czym polegają zmiany ?

Kluczowe są 3 różnice pomiędzy silnym typem wyliczeniowym, a jego poprzednikiem. W przypadku enum class:

  • nie wystąpi niejawna konwersja wartości do typu całkowitego (i dalej, np. do typu zmiennoprzecinkowego)
  • wartości nie zostaną wyeksportowane do zawierającego typ wyliczeniowy zakresu
  • bazowy typ wartości może być jawnie zdefiniowany, ale musi to być jeden z typów całkowitych, ze znakiem lub bez (domyślnie jest to int)

Zobaczmy, jak nowości wyglądają za żywo.

Pierwszy przykład:

Powyższy kod obrazuje pierwszy punkt na liście. Nie mamy tutaj w przypadku enum class niejawnych konwersji. Gdybyśmy jednak użyli starego typu wyliczeniowego, linia numer 11. skompiluje się bez problemów – nastąpi konwersja do typu całkowitego.

Dlaczego zatem zajęto się tą kwestią ? Niejawna konwersja może powodować problemy, jeśli nie chcemy, by typ zachowywał się jak liczba całkowita. Przed standardem C++11 było to domyślne zachowanie. Nowe rozwiązanie jest wg mnie bezpieczniejsze i bardziej zrozumiałe.

Spójrzmy na drugi przykład:

W drugim przykładzie mamy zobrazowany drugi punt z listy. W przypadku enum class wartości nie są eksportowane do zawierającego zakresu. Z tego powodu musimy użyć nazwy zdefiniowanego typu jako zakresu.

Co nam daje ta zmiana ? Pozwoli nam uniknąć konfliktu nazw w zakresie zawierającym oraz zwiększy czytelność kodu. Uważam to za bardzo dobrą zmianę.

Wreszcie przykład numer 3:

Widzimy tutaj, jak jawnie zadeklarować typ bazowy dla typu wyliczeniowego. Dzięki temu możliwe staje się stwierdzenie, ile dokładnie miejsca w pamięci zajmie wyliczenie.

Drugą ważną zaletą jest możliwość skorzystania z tzw. deklaracji wyprzedzającej (ang. forward declaration), czyli:

Powyższy kod jest w C++11 jak najbardziej poprawny.

Podsumowanie.

Silny typ wyliczeniowy to taki enum na sterydach. Zmiany są jednak przemyślane i wprowadzają nową jakość oraz lepszą odporność na błędy w przypadku użycia wyliczeń w naszym kodzie.

Można powiedzieć, że nieprzypadkowo silne typy wyliczeniowe definiuje się parą słów kluczowych enum class. Łączą one w sobie własności poprzednika (umożliwiają definiowanie nazwanych wartości) oraz właściwości klas (brak konwersji, pola chronione zakresem).

Nowy typ wyliczeniowy jest już w tej chwili stosowany w wielu miejscach w bibliotece standardowej języka. Oczywiście z powodu kompatybilności wstecznej, nadal możemy korzystać ze starszego rozwiązania. Czy ma to sens ? Moim zdaniem nie ma żadnego 🙂