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 classesenumsHier 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;
}
enum classEs sollte bevorzugt werden, da sie weniger Überraschungen verursachen, die möglicherweise zu Fehlern führen können.
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?
enum classwar, 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 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?:
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 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??)
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_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.
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.
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
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 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.
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
};