Ich habe gehört, dass einige Leute empfehlen, in C++ enum Klassen zu verwenden, wegen ihrer Typsicherheit.
Aber was bedeutet das wirklich?
Ich habe gehört, dass einige Leute empfehlen, in C++ enum Klassen zu verwenden, wegen ihrer Typsicherheit.
Aber was bedeutet das wirklich?
C++ hat zwei Arten von enum
:
enum class
esenum
sHier sind ein paar Beispiele, wie man sie deklariert:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // einfaches enum
Was ist der Unterschied zwischen den beiden?
enum class
es - Enumerator-Namen sind lokal zum Enum und ihre Werte konvertieren nicht implizit zu anderen Typen (wie einem anderen enum
oder int
)
Einfache enum
s - wo Enumerator-Namen im gleichen Gültigkeitsbereich wie das Enum sind und ihre Werte implizit zu Ganzzahlen und anderen Typen konvertieren
Beispiel:
enum Color { red, green, blue }; // einfaches enum
enum Card { red_card, green_card, yellow_card }; // ein anderes einfaches enum
enum class Animal { dog, deer, cat, bird, human }; // enum class
enum class Mammal { kangaroo, deer, human }; // ein weiteres enum class
void fun() {
// Beispiele für falsche Verwendung von einfachen enums:
Color color = Color::red;
Card card = Card::green_card;
int num = color; // kein Problem
if (color == Card::red_card) // kein Problem (falsch)
cout << "falsch" << endl;
if (card == Color::green) // kein Problem (falsch)
cout << "falsch" << endl;
// Beispiele für korrekte Verwendung von enum classes (sicher)
Animal a = Animal::deer;
Mammal m = Mammal::deer;
int num2 = a; // Fehler
if (m == a) // Fehler (korrekt)
cout << "falsch" << endl;
if (a == Mammal::deer) // Fehler (korrekt)
cout << "falsch" << endl;
}
enum class
es sollten bevorzugt werden, da sie weniger Überraschungen verursachen, die potenziell zu Fehlern führen könnten.
Von Bjarne Stroustrup's C++11 FAQ:
Die
enum class
("neue Enums", "starke Enums") adressieren drei Probleme traditioneller C++-Aufzählungen:
- konventionelle Enums konvertieren implizit zu int, was zu Fehlern führt, wenn jemand nicht möchte, dass eine Aufzählung als Ganzzahl fungiert.
- konventionelle Enums exportieren ihre Enumeratoren in den umgebenden Bereich, was zu Namenskonflikten führt.
- Der zugrunde liegende Typ eines
enum
kann nicht spezifiziert werden, was Verwirrung, Kompatibilitätsprobleme verursacht und Deklarationen im Voraus unmöglich macht.Die neuen Enums sind "enum class", weil sie Aspekte traditioneller Aufzählungen (benannte Werte) mit Aspekten von Klassen (abgegrenzten Elementen und fehlenden Konvertierungen) kombinieren.
Also, wie von anderen Benutzern erwähnt, würden die "starken Enums" den Code sicherer machen.
Der zugrunde liegende Typ eines "klassischen" enum
soll ein groß genuges Ganzzahlentyp sein, um alle Werte des enum
zu speichern; dies ist normalerweise ein int
. Auch jedes aufgezählte Typ soll mit char
oder einem vorzeichenbehafteten/vorzeichenlosen Ganzzahlentyp kompatibel sein.
Dies ist eine umfassende Beschreibung dessen, was der zugrunde liegende Typ eines enum
sein muss, daher wird jeder Compiler selbst Entscheidungen über den zugrunde liegenden Typ des klassischen enum
treffen, und manchmal könnte das Ergebnis überraschend sein.
Zum Beispiel habe ich Code wie diesen schon mehrmals gesehen:
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?
};
In dem obigen Code denkt ein naiver Entwickler, dass der Compiler die Werte von E_MY_FAVOURITE_FRUITS
in einen unsigned 8-Bit-Typ speichern wird... aber es gibt keine Garantie dafür: der Compiler kann unsigned char
oder int
oder short
wählen, jeder dieser Typen ist groß genug, um alle Werte im enum
zu speichern. Das Hinzufügen des Felds E_MY_FAVOURITE_FRUITS_FORCE8
ist eine Belastung und zwingt den Compiler nicht zu irgendeiner Art der Auswahl des zugrunde liegenden Typs des enum
.
Wenn es irgendeinen Code gibt, der von der Typengröße abhängig ist und/oder annimmt, dass E_MY_FAVOURITE_FRUITS
eine bestimmte Breite hätte (z. B. Serialisierungsroutinen), könnte dieser Code je nach den Gedanken des Compilers auf seltsame Weise funktionieren.
Und um die Dinge noch schlimmer zu machen, wenn ein Arbeitskollege leichtsinnig einen neuen Wert zu unserem enum
hinzufügt:
E_DEVIL_FRUIT = 0x100, // Neue Frucht, mit Wert größer als 8 Bits
Der Compiler beschwert sich nicht! Er ändert einfach die Größe des Typs, um alle Werte des enum
zu speichern (unter der Annahme, dass der Compiler den kleinstmöglichen Typ verwendet hat, was eine Annahme ist, die wir nicht treffen können). Diese einfache und leichtfertige Ergänzung zum enum
könnte verwandten Code subtil brechen.
Seit C++11 ist es möglich, den zugrunde liegenden Typ für enum
und enum class
anzugeben (danke rdb), so dass dieses Problem sauber adressiert wird:
enum class E_MY_FAVOURITE_FRUITS : unsigned char
{
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_DEVIL_FRUIT = 0x100, // Achtung!: Konstantenwert abgeschnitten
};
Indem man den zugrunde liegenden Typ spezifiziert, wenn ein Feld einen Ausdruck außerhalb des Bereichs dieses Typs hat, wird der Compiler sich beschweren, anstatt den zugrunde liegenden Typ zu ändern.
Ich denke, dass dies eine gute Sicherheitsverbesserung ist.
Warum wird also enum class gegenüber einfachem enum bevorzugt, wenn wir den zugrunde liegenden Typ für abgegrenzte (enum class
) und unabhängige (enum
) Enums wählen können, was macht enum class noch zur besseren Wahl?:
int
.Der grundlegende Vorteil der Verwendung einer enum class gegenüber normalen enums besteht darin, dass Sie die gleichen enum-Variablen für 2 verschiedene enums haben können und diese dennoch auflösen können (was vom OP als typsicher bezeichnet wurde)
Zum Beispiel:
enum class Color1 { red, green, blue }; //das wird kompilieren
enum class Color2 { red, green, blue };
enum Color1 { red, green, blue }; //das wird nicht kompilieren
enum Color2 { red, green, blue };
Für die grundlegenden enums kann der Compiler nicht unterscheiden, ob red
auf den Typ Color1
oder Color2
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Kompilierzeitfehler (auf welches red beziehen Sie sich??)
Enumerationen werden verwendet, um eine Menge von Ganzzahlen darzustellen.
Das class
-Schlüsselwort nach dem enum
gibt an, dass die Aufzählung stark typisiert ist und dass ihre Elemente abgegrenzt sind. Auf diese Weise verhindern enum
- Klassen eine versehentliche falsche Verwendung von Konstanten.
Zum Beispiel:
enum class Tier{Hund, Katze, Tiger};
enum class Haustiere{Hund, Papagei};
Hier können wir keine Werte von Tier und Haustiere mischen.
Tier t = Hund; // Fehler: Welcher Hund?
Tier t = Haustiere::Hund // Haustiere::Hund ist kein Tier
Zu beachten ist, dass C++20 neben den anderen Antworten ein Problem löst, das enum class
hat: die Verbosität. Man stelle sich ein hypothetisches enum class
namens Color
vor.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// usw.
}
}
Dies ist im Vergleich zur einfachen enum
-Variante umständlich, da die Namen im globalen Scope sind und daher nicht mit Color::
vorangestellt werden müssen.
In C++20 können wir jedoch using enum
verwenden, um alle Namen in einem enum in den aktuellen Scope einzuführen und das Problem zu lösen.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// usw.
}
}
Jetzt gibt es also keinen Grund mehr, enum class
nicht zu verwenden.
CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.