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:

enum class Level 
{ 
    First, 
    Second, 
    Third 
}; 

Level myLevel = Level::Second;

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:

int main() 
{   
    enum class Level   
    {       
        First,       
        Second,       
        Third   
    };      

    Level level = 2;    // błąd: brak konwersji int-->Level   
    int e1 = Level::Second;    // błąd: brak konwersji Level-->int      

    return 0; 
}

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:

int main() 
{   
    enum class Level   
    {       
        First,       
        Second,       
        Third   
    };      

    Level e1 = First;    // błąd: Co to jest First ? Nie wiadomo...   
    Level e2 = Level::Second;    // ok      

    return 0; 
}

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:

enum class Value : long 
{     
    Value1 = 4,     
    Value2 = 7,     
    Value3 = 9999999999 
};

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:

enum class Value : long;  

void function(Value* a_value);  

enum class Value : long 
{     
    Value1 = 4,     
    Value2 = 7,     
    Value3 = 9999999999 
};

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 🙂