Ik hoorde een paar mensen aanbevelen om enum klassenin C++ te gebruiken vanwege hun typeveiligheid.
Maar wat betekent dat eigenlijk?
Antwoord 1, autoriteit 100%
C++ heeft twee soorten enum
:
enum class
es- Gewone
enum
s
Hier zijn een paar voorbeelden om ze te declareren:
enum class Color { red, green, blue }; // enum class
enum Animal { dog, cat, bird, human }; // plain enum
Wat is het verschil tussen de twee?
-
enum class
es – enumeratornamen zijn lokaalvoor de enum en hun waarden worden nietimpliciet geconverteerd naar andere typen (zoals een andereenum
ofint
) -
Gewone
enum
s – waarbij enumeratornamen binnen hetzelfde bereik vallen als de opsomming en hun
waarden worden impliciet omgezet in gehele getallen en andere typen
Voorbeeld:
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;
}
Conclusie:
enum class
ES zou de voorkeur moeten hebben omdat ze minder verrassingen veroorzaken die mogelijk kunnen leiden tot bugs.
2, Autoriteit 48%
van bjarne stressrup’s C++ 11 FAQ :
De
enum class
ES (“Nieuwe ENUMS”, “Strong Enums”) Adres drie problemen
Met traditionele C++ -verordeningen:
- Conventionele ENUMS Converteer impliciet naar INT, waardoor er fouten zijn wanneer iemand geen opsomming is om als geheel getal op te treden.
- Conventionele ENUM’s exporteren hun middelen naar de omringende reikwijdte, waardoor de naam botsingen.
- Het onderliggende type van een
enum
kan niet worden gespecificeerd, waardoor verwarring, compatibiliteitsproblemen veroorzaakt en de aangifte
onmogelijk.De nieuwe ENUM’s zijn “ENUM-klasse” omdat ze aspecten van traditionele instructies (namenwaarden) combineren met aspecten van klassen (scoped-leden en afwezigheid van conversies).
Dus, zoals vermeld door andere gebruikers, zouden de “sterke ENUMS” de code veiliger maken.
Het onderliggende type van een “klassieke” enum
is een geheel getal-type dat groot genoeg is om te passen bij alle waarden van de enum
; Dit is meestal een int
. Ook elk opgesomd type is compatibel met char
of een ondertekend / niet-ondertekend geheel getal.
Dit is een brede beschrijving van wat een enum
onderliggend type moet zijn, dus elke compiler zal zelf beslissingen nemen over het onderliggende type van de klassieke enum
en soms het resultaat kan verrassend zijn.
Ik heb bijvoorbeeld een aantal keer een dergelijke code gezien:
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 de bovenstaande code denkt een naïeve codeur dat de compiler de E_MY_FAVOURITE_FRUITS
-waarden zal opslaan in een niet-ondertekend 8-bits type… maar er is geen garantie: de compiler kan kiezen voor unsigned char
of int
of short
, elk van deze typen is groot genoeg om aan alle waarden in de enum
te passen. Het toevoegen van het veld E_MY_FAVOURITE_FRUITS_FORCE8
is een last en dwingt de compiler niet om enige keuze te maken over het onderliggende type van de enum
.
Als er een stukje code is dat afhankelijk is van de grootte van het type en/of ervan uitgaat dat E_MY_FAVOURITE_FRUITS
een bepaalde breedte heeft (bijvoorbeeld: serialisatieroutines), kan deze code zich op een vreemde manier gedragen, afhankelijk van de compiler gedachten.
En om het nog erger te maken, als een collega achteloos een nieuwe waarde toevoegt aan onze enum
:
E_DEVIL_FRUIT = 0x100, // New fruit, with value greater than 8bits
De compiler klaagt er niet over! Het past alleen het type aan zodat het in alle waarden van de enum
past (ervan uitgaande dat de compiler het kleinst mogelijke type gebruikte, wat een aanname is die we niet kunnen doen). Deze eenvoudige en onzorgvuldige toevoeging aan de enum
kan gerelateerde code subtiel breken.
Sinds C++11 het mogelijk is om het onderliggende type te specificeren voor enum
en enum class
(bedankt rdb) dus dit probleem is netjes opgelost:
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
};
Het onderliggende type specificeren als een veld een expressie heeft die buiten het bereik van dit type valt, zal de compiler klagen in plaats van het onderliggende type te wijzigen.
Ik denk dat dit een goede veiligheidsverbetering is.
Dus Waarom heeft de enum-klasse de voorkeur boven de gewone enum?, als we het onderliggende type kunnen kiezen voor scoped(enum class
) en unscoped (enum
) enums wat maakt enum class
nog meer een betere keuze?:
- Ze converteren niet impliciet naar
int
. - Ze vervuilen de omringende naamruimte niet.
- Ze kunnen doorgestuurd worden.
Antwoord 3, autoriteit 9%
Het basisvoordeel van het gebruik van enum-klasse ten opzichte van normale enums is dat je dezelfde enum-variabelen kunt hebben voor 2 verschillende enums en ze nog steeds kunt oplossen (wat door OP is genoemd als type safe)
Voor bijvoorbeeld:
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 };
Wat betreft de basis ENUMS, kan Compiler niet onderscheiden of red
verwijst naar het type Color1
of Color2
zoals in HTE onderstaande verklaring.
enum Color1 { red, green, blue };
enum Color2 { red, green, blue };
int x = red; //Compile time error(which red are you refering to??)
4, Autoriteit 2%
- Converteer niet impliciet naar int
- kan kiezen welk type underlie
- Enum NameSpace om vervuiling te voorkomen, gebeuren
- Vergeleken met normale klasse, kan naar voren worden aangegeven, maar geen methoden
is
hebben
5, Autoriteit 2%
Het is vermeldenswaard, naast deze andere antwoorden, dat C++20 een van de problemen oplost die enum class
heeft: breedsprakigheid. Stel je een hypothetische enum class
, Color
voor.
void foo(Color c)
switch (c) {
case Color::Red: ...;
case Color::Green: ...;
case Color::Blue: ...;
// etc
}
}
Dit is uitgebreid vergeleken met de gewone variant enum
, waarbij de namen in het globale bereik staan en daarom niet voorafgegaan hoeven te worden door Color::
.
In C++20 kunnen we echter using enum
gebruiken om alle namen in een enum in te voeren in de huidige scope, waardoor het probleem wordt opgelost.
void foo(Color c)
using enum Color;
switch (c) {
case Red: ...;
case Green: ...;
case Blue: ...;
// etc
}
}
Dus nu is er geen reden om enum class
niet te gebruiken.
Antwoord 6
C++11 FAQvermeldt onderstaande punten:
conventionele opsommingen worden impliciet omgezet in int, wat fouten veroorzaakt wanneer iemand niet wil dat een opsomming als een geheel getal fungeert.
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;
}
conventionele opsommingen exporteren hun enumerators naar het omringende bereik, waardoor naamconflicten ontstaan.
// 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
};
Het onderliggende type van een opsomming kan niet worden gespecificeerd, wat verwarring en compatibiliteitsproblemen veroorzaakt en het doorsturen onmogelijk maakt.
// 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;
}
Antwoord 7
Omdat, zoals gezegd in andere antwoorden, class enum niet impliciet kan worden omgezet in int/bool, helpt het ook om buggy-code te vermijden zoals:
enum MyEnum {
Value1,
Value2,
};
...
if (var == Value1 || Value2) // Should be "var == Value2" no error/warning
Antwoord 8
Eén ding dat niet expliciet is genoemd: de scope-functie geeft je een optie om dezelfde naam te hebben voor een enum- en class-methode. Bijvoorbeeld:
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
};