Warum wird die Aufzählungsklasse der einfachen Aufzählung vorgezogen?


429

Ich hörte ein paar Leute empfehlen , der ENUM zu nutzen Klassen in C ++ aufgrund ihrer Art Sicherheit .

Aber was bedeutet das wirklich?


57
Wenn jemand behauptet, ein Programmierkonstrukt sei "böse", versucht er, Sie davon abzuhalten, selbst zu denken.
Pete Becker

3
@NicolBolas: Dies ist eher eine rethorische Frage, um eine FAQ-Antwort zu geben (ob dies wirklich häufig gestellt wird , ist eine andere Geschichte).
David Rodríguez - Dribeas

@ David, es gibt eine Diskussion, ob dies eine FAQ sein soll oder nicht, die hier beginnt . Eingabe willkommen.
sbi

17
@PeteBecker Manchmal versuchen sie nur, dich vor dir selbst zu schützen .
Piccy

geeksforgeeks.org/... Dies ist auch ein guter Ort , um zu verstehen enumvs enum class.
mr_azad

Antworten:


471

C ++ hat zwei Arten von enum:

  1. enum classes
  2. Plain enums

Hier sind einige Beispiele, wie man sie deklariert:

 enum class Color { red, green, blue }; // enum class
 enum Animal { dog, cat, bird, human }; // plain enum 

Was ist der Unterschied zwischen zwei?

  • enum classes - Enumerator-Namen sind lokal für die Aufzählung und ihre Werte werden nicht implizit in andere Typen konvertiert (wie andere enumoder int)

  • Einfache enums - wobei Enumeratornamen im selben Bereich wie die Aufzählung liegen und ihre Werte implizit in Ganzzahlen und andere Typen konvertiert werden

Beispiel:

enum Color { red, green, blue };                    // plain enum 
enum Card { red_card, green_card, yellow_card };    // another plain enum 
enum class Animal { dog, deer, cat, bird, human };  // enum class
enum class Mammal { kangaroo, deer, human };        // another enum class

void fun() {

    // examples of bad use of plain enums:
    Color color = Color::red;
    Card card = Card::green_card;

    int num = color;    // no problem

    if (color == Card::red_card) // no problem (bad)
        cout << "bad" << endl;

    if (card == Color::green)   // no problem (bad)
        cout << "bad" << endl;

    // examples of good use of enum classes (safe)
    Animal a = Animal::deer;
    Mammal m = Mammal::deer;

    int num2 = a;   // error
    if (m == a)         // error (good)
        cout << "bad" << endl;

    if (a == Mammal::deer) // error (good)
        cout << "bad" << endl;

}

Fazit:

enum classEs sollte bevorzugt werden, da sie weniger Überraschungen verursachen, die möglicherweise zu Fehlern führen können.


7
Gutes Beispiel ... gibt es eine Möglichkeit, die Typensicherheit der Klassenversion mit der Namespace-Promotion der Enum-Version zu kombinieren? Das heißt, wenn ich eine Klasse Amit Status habe und enum class State { online, offline };als Kind eine Klasse erstelle A, möchte ich state == onlineinnerhalb Avon state == State::online... Prüfungen durchführen, anstatt ... ist das möglich?
Markieren Sie den

31
Nee. Die Namespace-Promotion ist ein Bad Thing ™ und die halbe Rechtfertigung dafür enum classwar, sie zu beseitigen.
Welpe

10
In C ++ 11 können Sie auch explizit typisierte Aufzählungen verwenden, wie z. B. Aufzählung Animal: unsigned int {Hund, Hirsch, Katze, Vogel}
Blasius Secundus

3
@Cat Plus Plus Ich verstehe, dass @Oleksiy sagt, dass es schlecht ist. Meine Frage war nicht, ob Oleksiy es für schlecht hielt. Meine Frage war eine Bitte, genau zu beschreiben, was daran schlecht ist. Insbesondere, warum hält Oleksiy zum Beispiel schlecht für schlecht Color color = Color::red.
chux

9
@Cat Plus Plus Das schlechte Beispiel tritt also erst in der if (color == Card::red_card)Zeile auf, 4 Zeilen später als der Kommentar (der jetzt für die erste Hälfte des Blocks gilt). 2 Zeilen des Blocks geben die schlechten Beispiele an. Die ersten 3 Zeilen sind kein Problem. Der "ganze Block ist, warum einfache Aufzählungen schlecht sind" warf mich, als ich dachte, Sie meinten, dass etwas mit diesen auch nicht stimmte. Ich sehe jetzt, es ist nur ein Setup. Auf jeden Fall danke für das Feedback.
chux

248

Aus den C ++ 11-FAQ von Bjarne Stroustrup :

Die enum classes ("neue Aufzählungen", "starke Aufzählungen") behandeln drei Probleme mit herkömmlichen C ++ - Aufzählungen:

  • Herkömmliche Aufzählungen werden implizit in int konvertiert und verursachen Fehler, wenn jemand nicht möchte, dass eine Aufzählung als Ganzzahl fungiert.
  • Herkömmliche Aufzählungen exportieren ihre Enumeratoren in den umgebenden Bereich, was zu Namenskonflikten führt.
  • Der zugrunde liegende Typ von a enumkann nicht angegeben werden, was zu Verwirrung und Kompatibilitätsproblemen führt und eine Vorwärtsdeklaration unmöglich macht.

Die neuen Aufzählungen sind "Aufzählungsklassen", da sie Aspekte traditioneller Aufzählungen (Namenswerte) mit Aspekten von Klassen (Gültigkeitsbereichsmitglieder und fehlende Konvertierungen) kombinieren.

Wie von anderen Benutzern erwähnt, würden die "starken Aufzählungen" den Code sicherer machen.

Der zugrunde liegende Typ eines "Klassikers" enummuss ein ganzzahliger Typ sein, der groß genug ist, um alle Werte des zu enumerfüllen. Dies ist in der Regel eine int. Außerdem muss jeder Aufzählungstyp mit chareinem vorzeichenbehafteten / vorzeichenlosen Ganzzahltyp kompatibel sein .

Dies ist eine umfassende Beschreibung dessen, was ein enumzugrunde liegender Typ sein muss, sodass jeder Compiler selbst Entscheidungen über den zugrunde liegenden Typ des Klassikers treffen kann enumund das Ergebnis manchmal überraschend sein kann.

Zum Beispiel habe ich Code wie diesen ein paar Mal 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?
};

Im obigen Code denkt ein naiver Codierer, dass der Compiler die E_MY_FAVOURITE_FRUITSWerte in einem vorzeichenlosen 8-Bit-Typ speichert ... aber es gibt keine Garantie dafür: Der Compiler kann wählen unsigned charoder intoder short, jeder dieser Typen ist groß genug, um alle zu passen Werte in der gesehen enum. Das Hinzufügen des Feldes E_MY_FAVOURITE_FRUITS_FORCE8ist eine Belastung und zwingt den Compiler nicht dazu, eine Auswahl über den zugrunde liegenden Typ des zu treffen enum.

Wenn es einen Code gibt, der von der Typgröße abhängt und / oder davon ausgeht, dass er E_MY_FAVOURITE_FRUITSeine gewisse Breite hat (z. B. Serialisierungsroutinen), kann sich dieser Code abhängig von den Gedanken des Compilers auf seltsame Weise verhalten.

Und um die Sache noch schlimmer zu machen, wenn ein Arbeitskollege nachlässig einen neuen Wert für unsere enum:

    E_DEVIL_FRUIT  = 0x100, // New fruit, with value greater than 8bits

Der Compiler beschwert sich nicht darüber! Der Typ wird nur so angepasst, dass er allen Werten von entspricht enum(vorausgesetzt, der Compiler verwendet den kleinstmöglichen Typ, was eine Annahme ist, die wir nicht tun können). Diese einfache und nachlässige Ergänzung des enumCodes könnte den Code subtil beschädigen.

Da es in C ++ 11 möglich ist, den zugrunde liegenden Typ für enumund enum class(danke rdb ) anzugeben , wird dieses Problem sorgfältig behoben :

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, // Warning!: constant value truncated
};

Wenn der Compiler einen Ausdruck außerhalb des Bereichs dieses Typs angibt, beschwert sich der Compiler, anstatt den zugrunde liegenden Typ zu ändern.

Ich denke, dass dies eine gute Sicherheitsverbesserung ist.

So Warum ist Enum Klasse über Normal Enum bevorzugt? , wenn wir den zugrunde liegenden Typ für Aufzählungen mit Gültigkeitsbereich ( enum class) und ohne Gültigkeitsbereich ( enum) auswählen können, was macht dann noch enum classeine bessere Wahl?:

  • Sie konvertieren nicht implizit in int.
  • Sie verschmutzen den umgebenden Namespace nicht.
  • Sie können vorwärts deklariert werden.

1
Ich nehme an, wir können den Enum-Basistyp auch für reguläre Enums einschränken, solange wir C ++ 11
Sagar Padhye

11
Entschuldigung, aber diese Antwort ist falsch. "enum class" hat nichts mit der Möglichkeit zu tun, den Typ anzugeben. Dies ist eine unabhängige Funktion, die sowohl für reguläre Aufzählungen als auch für Aufzählungsklassen vorhanden ist.
Rdb

14
Dies ist der Deal: * Enum-Klassen sind eine neue Funktion in C ++ 11. * Typisierte Aufzählungen sind eine neue Funktion in C ++ 11. Dies sind zwei separate, nicht verwandte neue Funktionen in C ++ 11. Sie können beide verwenden, oder Sie können entweder oder keine verwenden.
Rdb

2
Ich denke, dass Alex Allain die vollständigste einfache Erklärung liefert, die ich bisher in diesem Blog unter [ cprogramming.com/c++11/… gesehen habe . Die herkömmliche Aufzählung war gut geeignet, um Namen anstelle von ganzzahligen Werten zu verwenden und die Verwendung von Präprozessor #defines zu vermeiden, was eine gute Sache war - es fügte Klarheit hinzu. Enum - Klasse entfernt das Konzept eines numerischen Werts des Enumerators und führt Umfang und starke Typisierung zunimmt (na ja, kann erhöhen :-) Programm Korrektheit. Es bringt Sie dem objektorientierten Denken einen Schritt näher.
Jon Spencer

2
Abgesehen davon ist es immer amüsant, wenn Sie Code überprüfen und plötzlich One Piece passiert.
Justin Time - Stellen Sie Monica

47

Der grundlegende Vorteil der Verwendung der Enum-Klasse gegenüber normalen Enums besteht darin, dass Sie möglicherweise dieselben Enum-Variablen für 2 verschiedene Enums haben und diese dennoch auflösen können (was von OP als typsicher erwähnt wurde).

Zum Beispiel:

enum class Color1 { red, green, blue };    //this will compile
enum class Color2 { red, green, blue };

enum Color1 { red, green, blue };    //this will not compile 
enum Color2 { red, green, blue };

Bei den Basisaufzählungen kann der Compiler nicht unterscheiden, ob er redsich auf den Typ bezieht Color1oder Color2wie in der folgenden Anweisung beschrieben.

enum Color1 { red, green, blue };   
enum Color2 { red, green, blue };
int x = red;    //Compile time error(which red are you refering to??)

1
@Oleksiy Ohh, ich habe deine Frage nicht richtig gelesen. Betrachten Sie es als Add-On für diejenigen, die es nicht wussten.
Saksham

Es ist in Ordnung! Ich hätte das fast vergessen
Oleksiy

Natürlich würden Sie schreiben enum { COLOR1_RED, COLOR1_GREE, COLOR1_BLUE }und Namespace-Probleme leicht vermeiden. Das Namespace-Argument ist eines der drei hier genannten, die ich überhaupt nicht kaufe.
Jo So

2
@ Jo So Diese Lösung ist eine unnötige Problemumgehung. Aufzählung: enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }ist vergleichbar mit Aufzählungsklasse : enum class Color1 { RED, GREEN, BLUE }. Der Zugriff ist ähnlich: COLOR1_REDvs Color1::RED, aber in der Enum-Version müssen Sie in jeden Wert "COLOR1" eingeben, wodurch mehr Platz für Tippfehler vorhanden ist, was durch das Namespace-Verhalten einer Enum-Klasse vermieden wird.
CDgraham

2
Bitte konstruktive Kritik üben . Wenn ich mehr Platz für Tippfehler sage, meine ich, wenn Sie ursprünglich die Werte definieren, deren Werte enum Color1ein Compiler nicht erfassen kann, da es sich wahrscheinlich immer noch um einen "gültigen" Namen handelt. Wenn ich schreibe RED, GREENund so weiter eine ENUM - Klasse, als es nicht lösen , enum Bananaweil es erfordert , dass Sie angeben , Color1::REDum den Wert zuzugreifen (der Namensraum Argument). Es gibt immer noch gute Zeiten enum, aber das Namespace-Verhalten von enum classkann oft sehr vorteilhaft sein.
CDgraham

20

Aufzählungen werden verwendet, um eine Reihe von ganzzahligen Werten darzustellen.

Das classSchlüsselwort nach dem enumgibt an, dass die Aufzählung stark typisiert ist und ihre Aufzähler einen Gültigkeitsbereich haben. Auf diese Weise enumverhindern Klassen einen versehentlichen Missbrauch von Konstanten.

Zum Beispiel:

enum class Animal{Dog, Cat, Tiger};
enum class Pets{Dog, Parrot};

Hier können wir keine Tier- und Haustierwerte mischen.

Animal a = Dog;       // Error: which DOG?    
Animal a = Pets::Dog  // Pets::Dog is not an Animal

7

In den häufig gestellten Fragen zu C ++ 11 werden folgende Punkte erwähnt:

Herkömmliche Aufzählungen werden implizit in int konvertiert und verursachen Fehler, wenn jemand nicht möchte, dass eine Aufzählung als Ganzzahl fungiert.

enum color
{
    Red,
    Green,
    Yellow
};

enum class NewColor
{
    Red_1,
    Green_1,
    Yellow_1
};

int main()
{
    //! Implicit conversion is possible
    int i = Red;

    //! Need enum class name followed by access specifier. Ex: NewColor::Red_1
    int j = Red_1; // error C2065: 'Red_1': undeclared identifier

    //! Implicit converison is not possible. Solution Ex: int k = (int)NewColor::Red_1;
    int k = NewColor::Red_1; // error C2440: 'initializing': cannot convert from 'NewColor' to 'int'

    return 0;
}

Herkömmliche Aufzählungen exportieren ihre Enumeratoren in den umgebenden Bereich, was zu Namenskonflikten führt.

// Header.h

enum vehicle
{
    Car,
    Bus,
    Bike,
    Autorickshow
};

enum FourWheeler
{
    Car,        // error C2365: 'Car': redefinition; previous definition was 'enumerator'
    SmallBus
};

enum class Editor
{
    vim,
    eclipes,
    VisualStudio
};

enum class CppEditor
{
    eclipes,       // No error of redefinitions
    VisualStudio,  // No error of redefinitions
    QtCreator
};

Der zugrunde liegende Typ einer Aufzählung kann nicht angegeben werden, was zu Verwirrung und Kompatibilitätsproblemen führt und eine Vorwärtsdeklaration unmöglich macht.

// Header1.h
#include <iostream>

using namespace std;

enum class Port : unsigned char; // Forward declare

class MyClass
{
public:
    void PrintPort(enum class Port p);
};

void MyClass::PrintPort(enum class Port p)
{
    cout << (int)p << endl;
}

.

// Header.h
enum class Port : unsigned char // Declare enum type explicitly
{
    PORT_1 = 0x01,
    PORT_2 = 0x02,
    PORT_3 = 0x04
};

.

// Source.cpp
#include "Header1.h"
#include "Header.h"

using namespace std;
int main()
{
    MyClass m;
    m.PrintPort(Port::PORT_1);

    return 0;
}

In C ++ 11 können auch "Nicht-Klassen" -Aufzählungen eingegeben werden. Die Probleme mit der Verschmutzung des Namespace usw. bestehen weiterhin.
Werfen

7
  1. nicht implizit in int konvertieren
  2. kann wählen, welcher Typ zugrunde liegt
  3. ENUM-Namespace zur Vermeidung von Umweltverschmutzung
  4. Kann im Vergleich zur normalen Klasse als vorwärts deklariert werden, verfügt jedoch nicht über Methoden

1

Da Klassenaufzählungen, wie in anderen Antworten erwähnt, nicht implizit in int / bool konvertierbar sind, hilft es auch, fehlerhaften Code wie den folgenden zu vermeiden:

enum MyEnum {
  Value1,
  Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning

2
Um meinen vorherigen Kommentar zu vervollständigen, beachten Sie, dass gcc jetzt eine Warnung namens -Wint-in-bool-context hat, die genau diese Art von Fehlern abfängt.
Arnaud

1

Zusätzlich zu diesen anderen Antworten ist anzumerken, dass C ++ 20 eines der Probleme löst, die es enum classgibt: Ausführlichkeit. Imagining eine hypothetische enum class, Color.

void foo(Color c)
  switch (c) {
    case Color::Red: ...;
    case Color::Green: ...;
    case Color::Blue: ...;
    // etc
  }
}

Dies ist ausführlich im Vergleich zu der einfachen enumVariante, bei der die Namen im globalen Bereich liegen und daher nicht vorangestellt werden müssen Color::.

In C ++ 20 können wir jedoch using enumalle Namen in einer Aufzählung in den aktuellen Bereich einfügen, um das Problem zu lösen.

void foo(Color c)
  using enum Color;
  switch (c) {
    case Red: ...;
    case Green: ...;
    case Blue: ...;
    // etc
  }
}

Es gibt also keinen Grund, nicht zu verwenden enum class.


0

Eine Sache, die nicht explizit erwähnt wurde - die Bereichsfunktion bietet Ihnen die Möglichkeit, denselben Namen für eine Aufzählungs- und Klassenmethode zu verwenden. Zum Beispiel:

class Test
{
public:
   // these call ProcessCommand() internally
   void TakeSnapshot();
   void RestoreSnapshot();
private:
   enum class Command // wouldn't be possible without 'class'
   {
        TakeSnapshot,
        RestoreSnapshot
   };
   void ProcessCommand(Command cmd); // signal the other thread or whatever
};
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.