Ich hörte ein paar Leute empfehlen , der ENUM zu nutzen Klassen in C ++ aufgrund ihrer Art Sicherheit .
Aber was bedeutet das wirklich?
Ich hörte ein paar Leute empfehlen , der ENUM zu nutzen Klassen in C ++ aufgrund ihrer Art Sicherheit .
Aber was bedeutet das wirklich?
Antworten:
C ++ hat zwei Arten von enum
:
enum class
esenum
sHier 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 class
es - Enumerator-Namen sind lokal für die Aufzählung und ihre Werte werden nicht implizit in andere Typen konvertiert (wie andere enum
oder int
)
Einfache enum
s - 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;
}
enum class
Es sollte bevorzugt werden, da sie weniger Überraschungen verursachen, die möglicherweise zu Fehlern führen können.
A
mit Status habe und enum class State { online, offline };
als Kind eine Klasse erstelle A
, möchte ich state == online
innerhalb A
von state == State::online
... Prüfungen durchführen, anstatt ... ist das möglich?
enum class
war, sie zu beseitigen.
Color color = Color::red
.
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.
Aus den C ++ 11-FAQ von Bjarne Stroustrup :
Die
enum class
es ("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
enum
kann 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" enum
muss ein ganzzahliger Typ sein, der groß genug ist, um alle Werte des zu enum
erfüllen. Dies ist in der Regel eine int
. Außerdem muss jeder Aufzählungstyp mit char
einem vorzeichenbehafteten / vorzeichenlosen Ganzzahltyp kompatibel sein .
Dies ist eine umfassende Beschreibung dessen, was ein enum
zugrunde liegender Typ sein muss, sodass jeder Compiler selbst Entscheidungen über den zugrunde liegenden Typ des Klassikers treffen kann enum
und 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_FRUITS
Werte in einem vorzeichenlosen 8-Bit-Typ speichert ... aber es gibt keine Garantie dafür: Der Compiler kann wählen unsigned char
oder int
oder 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_FORCE8
ist 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_FRUITS
eine 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 enum
Codes könnte den Code subtil beschädigen.
Da es in C ++ 11 möglich ist, den zugrunde liegenden Typ für enum
und 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 class
eine bessere Wahl?:
int
.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 red
sich auf den Typ bezieht Color1
oder Color2
wie 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??)
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.
enum Color1 { COLOR1_RED, COLOR1_GREEN, COLOR1_BLUE }
ist vergleichbar mit Aufzählungsklasse : enum class Color1 { RED, GREEN, BLUE }
. Der Zugriff ist ähnlich: COLOR1_RED
vs 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.
enum Color1
ein Compiler nicht erfassen kann, da es sich wahrscheinlich immer noch um einen "gültigen" Namen handelt. Wenn ich schreibe RED
, GREEN
und so weiter eine ENUM - Klasse, als es nicht lösen , enum Banana
weil es erfordert , dass Sie angeben , Color1::RED
um den Wert zuzugreifen (der Namensraum Argument). Es gibt immer noch gute Zeiten enum
, aber das Namespace-Verhalten von enum class
kann oft sehr vorteilhaft sein.
Aufzählungen werden verwendet, um eine Reihe von ganzzahligen Werten darzustellen.
Das class
Schlüsselwort nach dem enum
gibt an, dass die Aufzählung stark typisiert ist und ihre Aufzähler einen Gültigkeitsbereich haben. Auf diese Weise enum
verhindern 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
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;
}
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
Zusätzlich zu diesen anderen Antworten ist anzumerken, dass C ++ 20 eines der Probleme löst, die es enum class
gibt: 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 enum
Variante, bei der die Namen im globalen Bereich liegen und daher nicht vorangestellt werden müssen Color::
.
In C ++ 20 können wir jedoch using enum
alle 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
.
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
};