748 Stimmen

Warum wird die enum-Klasse der einfachen enum vorgezogen?

Ich habe gehört, dass einige Leute empfehlen, in C++ enum Klassen zu verwenden, wegen ihrer Typsicherheit.

Aber was bedeutet das wirklich?

837voto

Oleksiy Punkte 33680

C++ hat zwei Arten von enum:

  1. enum classes
  2. Einfache enums

Hier 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 classes - Enumerator-Namen sind lokal zum Enum und ihre Werte konvertieren nicht implizit zu anderen Typen (wie einem anderen enum oder int)

  • Einfache enums - 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;

}

Fazit:

enum classes sollten bevorzugt werden, da sie weniger Überraschungen verursachen, die potenziell zu Fehlern führen könnten.

336voto

PaperBirdMaster Punkte 12136

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?:

  • Sie konvertieren nicht implizit zu int.
  • Sie verschmutzen nicht den umgebenden Namespace.
  • Sie können vorwärtsdeklariert werden.

66voto

Saksham Punkte 8725

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??)

25voto

Alok151290 Punkte 251

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

23voto

themeeman Punkte 319

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.com

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.

Powered by:

X