C++ Core Guidelines: Regeln für Aufzählungen
Das Kapitel zu Aufzählungen besitzt acht Regeln. Seit C++11 kennt C++ Aufzählungen mit eigenem Gültigkeitsbereich, die die Nachteile der klassischen Aufzählungen überwinden.
- Rainer Grimm
Das Kapitel zu Aufzählungen besitzt acht Regeln. Seit C++11 kennt C++ Aufzählungen mit eigenem Gültigkeitsbereich (scoped enums), die die Nachteile der klassischen Aufzählungen überwinden.
Aufzählungen stellen eine Menge von Ganzzahlen dar, die sich wie ein Datentyp verhalten. Hier ist der Überblick zu den acht Regeln.
- Enum.1: Prefer enumerations over macros
- Enum.2: Use enumerations to represent sets of related named constants
- Enum.3: Prefer enum classes over “plain” enums
- Enum.4: Define operations on enumerations for safe and simple use
- Enum.5: Don’t use ALL_CAPS for enumerators
- Enum.6: Avoid unnamed enumerations
- Enum.7: Specify the underlying type of an enumeration only when necessary
- Enum.8: Specify enumerator values only when necessary
Wie im Vorspann genannt: Klassische Aufzählungen besitzen viele Nachteile. Daher will ich gerne explizit klassische Aufzählungen ohne eigenen Gültigkeitsbereich (unscoped enums) und Aufzählungen mit eigenem Gültigkeitsbereich, die gerne auch streng typisierte Aufzählungen genannt werden, vergleichen. In den C++ Core Guidelines fehlt mir dieser wichtige Vergleich.
Hier ist eine klassische Aufzählung:
enum Colour{
red,
blue,
green
};
Welche Nachteile besitzen sie?
- Die Aufzähler besitzen keinen eigenen Gültigkeitsbereich
- Die Aufzähler konvertieren heimlich zu int
- Die Aufzähler verschmutzen den globalen Namensbereich
- Der Typ der Aufzähler ist nicht definiert. Er muss nur groß genug sein, um die Werte der Aufzähler darzustellen.
Durch das Verwenden des Schlüsselwortes class oder struct wird die klassische Aufzählung zu einer Aufzählung mit Gültigkeitsbereich (scoped enum oder auch enum class).
enum class ColourScoped{
red,
blue,
green
};
Nun ist der Bereichsoperator :: notwendig, um auf die Aufzähler zuzugreifen: Colour::red. Colour::red konvertiert nicht heimlich zu int und verschmutzt daher auch nicht den globalen Namensbereich. Zusätzlich ist der Typ der Aufzähler per Default int.
Nach dieser Hintergrundinformation geht es direkt zu den Regeln.
Enum.1: Prefer enumerations over macros
Makros ignorieren Gültigkeitsbereich und besitzen keinen Datentyp. Das bedeutet, du kannst ein bereits gesetztes Makro, dass für eine Farbe steht, überschreiben.
// webcolors.h
#define RED 0xFF0000
// productinfo.h
#define RED 0
int webcolor = RED; // should be 0xFF0000
Mit der Aufzähler ColourScoped wird dieser Fehler nicht auftreten, denn er benötigt seinen eigenen Gültigkeitsbereich: ColourScoped webcolour = ColourScoped::red;
Enum.2: Use enumerations to represent sets of related named constants
Diese Regel ist leicht einsichtig, denn Aufzählungen sind Mengen von Ganzzahlen, die sich ein Datentyp verhalten.
Enum.3: Prefer enum classes over “plain” enums
Die Aufzähler einer Aufzählung mit Gültigkeitsbereich konvertieren nicht heimlich zu int. Sie müssen gegebenenfalls explizit konvertiert werden. Um auf sie zuzugreifen, ist der Bereichsoperator notwendig.
// scopedEnum.cpp
#include <iostream>
enum class ColourScoped{
red,
blue,
green
};
void useMe(ColourScoped color){
switch(color){
case ColourScoped::red:
std::cout << "ColourScoped::red" << std::endl;
break;
case ColourScoped::blue:
std::cout << "ColourScoped::blue" << std::endl;
break;
case ColourScoped::green:
std::cout << "ColourScoped::green" << std::endl;
break;
}
}
int main(){
std::cout << static_cast<int>(ColourScoped::red) << std::endl; // 0
std::cout << static_cast<int>(ColourScoped::red) << std::endl; // 0
std::cout << std::endl;
ColourScoped colour{ColourScoped::red};
useMe(colour); // ColourScoped::red
}
Enum.4: Define operations on enumerations for safe and simple use
Die Guidelines definiert zum Verdeutlichen ihrer Regel eine Aufzählung Day. Diese Aufzählung unterstützt den Inkrement-Operator.
enum Day { mon, tue, wed, thu, fri, sat, sun };
Day& operator++(Day& d)
{
return d =
(d == Day::sun) ? Day::mon : static_cast<Day>(static_cast<int>(d)+1);
}
Day today = Day::sat;
Day tomorrow = ++today;
Der static_cast in dem Beispiel ist notwendig. Falls der Inkrement-Operator im Inkrement-Operator zum Einsatz käme, wäre die Folge eine unendliche Rekursion.
Day& operator++(Day& d)
{
return d = (d == Day::sun) ? Day::mon : Day{++d}; // error
}
Enum.5: Don’t use ALL_CAPS for enumerators
Falls du ALL_CAPS für die Aufzähler verwendest, können Konflikte mit Makros auftreten, da diese typischerweise in Großbuchstaben geschrieben werden.
#define RED 0xFF0000
enum class ColourScoped{ RED }; // error
Enum.6: Avoid unnamed enumerations
Falls du keinen guten Namen für eine Aufzählung finden kannst, hängt das vermutlich damit zusammen, dass die Aufzähler keine semantische Beziehung zueinander haben. In diesem Fall solltest du constexpr Variablen einsetzen.
// bad
enum { red = 0xFF0000, scale = 4, is_signed = 1 };
// good
constexpr int red = 0xFF0000;
constexpr short scale = 4;
constexpr bool is_signed = true;
Enum.7: Specify the underlying type of an enumeration only when necessary
Seit C++11 lässt sich der zugrunde liegende Datentyp der Aufzähler angeben. Das spart gegebenenfalls Speicher. Standardmäßig ist der zugrunde liegende Datentyp einer Aufzählung mit Gültigkeitsbereich int. Damit lassen sich diese vorwärts deklarieren.
// typeEnum.cpp
#include <iostream>
enum class Colour1{
red,
blue,
green
};
enum struct Colour2: char {
red,
blue,
green
};
int main(){
std::cout << sizeof(Colour1) << std::endl; // 4
std::cout << sizeof(Colour2) << std::endl; // 1
}
Enum.8: Specify enumerator values only when necessary
Durch das explizite Setzen der Aufzähler kann es natürlich passieren, dass ein Wert zweimal vergeben wird. Die folgende Aufzählung Col2 hat genau dieses Problem.
enum class Col1 { red, yellow, blue };
enum class Col2 { red = 1, yellow = 2, blue = 2 }; // typo
enum class Month { jan = 1, feb, mar, apr, may, jun, jul, august, sep,
oct, nov, dec }; // starting with 1 is conventional
Wie geht's weiter?
Dieser Artikel war kurz und bündig. Du solltest aber die Meta-Regel im Kopf behalten: Verwende Aufzählung mit Gültigkeitsbereich!
Das nächste Kapitel der C++ Core Guidelines beschäftigt sich mit den rund 35 Regeln für Ressource Management. Das bedeutet, wir tauchen im nächsten Artikel mitten in den Herz von C++.
Weitere Informationen:
- Wahl des nächsten PDF-Päckchen. Auf meinem deutschen Blog und meinem englischen Blog finden gerade die Wahl zum nächsten PDF-Päckchen statt. Hier sind die Links zur Wahl:
- Deutscher Blog: Welches PDF-Päckchen soll ich zusammenstellen? Mach dein Kreuz!
- Englischer Blog: Which PDF bundle should I provide? Make your choice!