Waarom heeft de enum-klasse de voorkeur boven de gewone enum?

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:

  1. enum classes
  2. Gewone enums

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 classes – enumeratornamen zijn lokaalvoor de enum en hun waarden worden nietimpliciet geconverteerd naar andere typen (zoals een andere enumof int)

  • Gewone enums – 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 classES 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 classES (“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 enumkan 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” enumis 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 charof een ondertekend / niet-ondertekend geheel getal.

Dit is een brede beschrijving van wat een enumonderliggend type moet zijn, dus elke compiler zal zelf beslissingen nemen over het onderliggende type van de klassieke enumen 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 charof intof short, elk van deze typen is groot genoeg om aan alle waarden in de enumte passen. Het toevoegen van het veld E_MY_FAVOURITE_FRUITS_FORCE8is 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_FRUITSeen 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 enumpast (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 enumkan gerelateerde code subtiel breken.

Sinds C++11 het mogelijk is om het onderliggende type te specificeren voor enumen 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 classnog 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 redverwijst naar het type Color1of Color2zoals 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%

  1. Converteer niet impliciet naar int
  2. kan kiezen welk type underlie
  3. is

  4. Enum NameSpace om vervuiling te voorkomen, gebeuren
  5. Vergeleken met normale klasse, kan naar voren worden aangegeven, maar geen methoden
  6. hebben


5, Autoriteit 2%

Het is vermeldenswaard, naast deze andere antwoorden, dat C++20 een van de problemen oplost die enum classheeft: breedsprakigheid. Stel je een hypothetische enum class, Colorvoor.

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 enumgebruiken 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 classniet 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
};

Other episodes