spazi dei nomi per i tipi enum: best practice


102

Spesso sono necessari diversi tipi enumerati insieme. A volte, uno ha uno scontro di nomi. Mi vengono in mente due soluzioni: utilizzare uno spazio dei nomi o utilizzare nomi di elementi enum "più grandi". Tuttavia, la soluzione dello spazio dei nomi ha due possibili implementazioni: una classe fittizia con enumerazione annidata o uno spazio dei nomi completo.

Sto cercando pro e contro di tutti e tre gli approcci.

Esempio:

// oft seen hand-crafted name clash solution
enum eColors { cRed, cColorBlue, cGreen, cYellow, cColorsEnd };
enum eFeelings { cAngry, cFeelingBlue, cHappy, cFeelingsEnd };
void setPenColor( const eColors c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cColorBlue: //...
        //...
    }
 }


// (ab)using a class as a namespace
class Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
class Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
 }


 // a real namespace?
 namespace Colors { enum e { cRed, cBlue, cGreen, cYellow, cEnd }; };
 namespace Feelings { enum e { cAngry, cBlue, cHappy, cEnd }; };
 void setPenColor( const Colors::e c ) {
    switch (c) {
        default: assert(false);
        break; case Colors::cRed: //...
        break; case Colors::cBlue: //...
        //...
    }
  }

19
Prima di tutto,
userei

bella domanda, ho usato il metodo dello spazio dei nomi ....;)
MiniScalope

18
il prefisso "c" su tutto danneggia la leggibilità.
Utente

8
@User: perché, grazie per aver aperto la discussione ungherese :)
xtofl

5
Notare che non è necessario denominare l'enum come in enum e {...}, gli enum possono essere anonimi, ovvero enum {...}, il che ha molto più senso se racchiusi nello spazio dei nomi o nella classe.
kralyk

Risposte:


73

Risposta originale C ++ 03:

Il vantaggio di a namespace(rispetto a a class) è che puoi usare le usingdichiarazioni quando vuoi.

Il problema con l'utilizzo di a namespaceè che gli spazi dei nomi possono essere espansi altrove nel codice. In un progetto di grandi dimensioni, non sarebbe garantito che due enumerazioni distinte non pensino entrambe di essere chiamateeFeelings

Per un codice dall'aspetto più semplice, utilizzo a struct, poiché presumibilmente vuoi che i contenuti siano pubblici.

Se stai eseguendo una di queste pratiche, sei in anticipo sulla curva e probabilmente non hai bisogno di esaminarlo ulteriormente.

Consiglio più recente, C ++ 11:

Se si utilizza C ++ 11 o versioni successive, l' enum classambito implicito dei valori enum all'interno del nome dell'enumerazione.

Con enum classperderai conversioni implicite e confronti con tipi interi, ma in pratica ciò potrebbe aiutarti a scoprire codice ambiguo o difettoso.


4
Sono d'accordo con l'idea della struttura. E grazie per il complimento :)
xtofl

3
+1 Non riuscivo a ricordare la sintassi della "classe enum" di C ++ 11. Senza questa caratteristica, le enumerazioni sono incomplete.
Grault

È un'opzione per usare "using" per l'ambito implicito della "classe enum". es. aggiungerà "using Color :: e;" per codificare consentire l'uso di 'cRed' e sapere che dovrebbe essere Color :: e :: cRed?
JVA:

20

FYI In C ++ 0x c'è una nuova sintassi per casi come quello che hai menzionato (vedi la pagina wiki C ++ 0x )

enum class eColors { ... };
enum class eFeelings { ... };

11

Ho ibridato le risposte precedenti a qualcosa del genere: (EDIT: questo è utile solo per pre-C ++ 11. Se stai usando C ++ 11, usa enum class)

Ho un grande file di intestazione che contiene tutte le enumerazioni del mio progetto, perché queste enumerazioni sono condivise tra le classi di lavoro e non ha senso mettere le enumerazioni nelle stesse classi di lavoro.

La structevita il pubblico: sintattica di zucchero, e la typedeflascia in realtà dichiarare variabili di queste enumerazioni all'interno di altre classi operaie.

Non penso che l'uso di uno spazio dei nomi aiuti affatto. Forse questo è perché io sono un programmatore C #, e lì si hanno di utilizzare il nome di tipo enum quando ci si riferisce ai valori, quindi sono abituato ad esso.

    struct KeySource {
        typedef enum { 
            None, 
            Efuse, 
            Bbram
        } Type;
    };

    struct Checksum {
        typedef enum {
            None =0,
            MD5 = 1,
            SHA1 = 2,
            SHA2 = 3
        } Type;
    };

    struct Encryption {
        typedef enum {
            Undetermined,
            None,
            AES
        } Type;
    };

    struct File {
        typedef enum {
            Unknown = 0,
            MCS,
            MEM,
            BIN,
            HEX
        } Type;
    };

...

class Worker {
    File::Type fileType;
    void DoIt() {
       switch(fileType) {
       case File::MCS: ... ;
       case File::MEM: ... ;
       case File::HEX: ... ;
    }
}

9

Eviterei sicuramente di usare una classe per questo; usa invece uno spazio dei nomi. La domanda si riduce a se utilizzare uno spazio dei nomi o utilizzare ID univoci per i valori enum. Personalmente, userei uno spazio dei nomi in modo che i miei ID possano essere più brevi e, si spera, più autoesplicativi. Quindi il codice dell'applicazione potrebbe utilizzare una direttiva "using namespace" e rendere tutto più leggibile.

Dal tuo esempio sopra:

using namespace Colors;

void setPenColor( const e c ) {
    switch (c) {
        default: assert(false);
        break; case cRed: //...
        break; case cBlue: //...
        //...
    }
}

Potresti dare un suggerimento sul motivo per cui preferiresti uno spazio dei nomi a una classe?
xtofl

@xtofl: non puoi scrivere "using class Colors"
MSalters

2
@MSalters: non puoi nemmeno scrivere Colors someColor = Red;, perché lo spazio dei nomi non costituisce un tipo. Dovresti Colors::e someColor = Red;invece scrivere , il che è abbastanza controintuitivo.
SasQ

@SasQ Non dovresti usarlo Colors::e someColoranche con a struct/classse volessi usarlo in switchun'istruzione? Se utilizzi un anonimo, enumlo switch non sarà in grado di valutare un file struct.
Macbeth's Enigma

1
Scusa, ma mi const e csembra difficilmente leggibile :-) Non farlo. Tuttavia, l'utilizzo di uno spazio dei nomi va bene.
dhaumann

7

Una differenza tra l'utilizzo di una classe o di uno spazio dei nomi è che la classe non può essere riaperta come può fare uno spazio dei nomi. Ciò evita la possibilità che lo spazio dei nomi possa essere utilizzato in modo improprio in futuro, ma c'è anche il problema che non è nemmeno possibile aggiungere all'insieme di enumerazioni.

Un possibile vantaggio dell'utilizzo di una classe è che possono essere utilizzati come argomenti del tipo di modello, il che non è il caso degli spazi dei nomi:

class Colors {
public:
  enum TYPE {
    Red,
    Green,
    Blue
  };
};

template <typename T> void foo (T t) {
  typedef typename T::TYPE EnumType;
  // ...
}

Personalmente, non sono un fan dell'utilizzo e preferisco i nomi completi, quindi non lo vedo come un vantaggio per gli spazi dei nomi. Tuttavia, questa probabilmente non è la decisione più importante che prenderai nel tuo progetto!


Anche la mancata riapertura delle lezioni è un potenziale svantaggio. Anche l'elenco dei colori non è finito.
MSalters

1
Penso che non ripristinare una classe sia un potenziale vantaggio. Se voglio più colori, ricompilo semplicemente la classe con più colori. Se non posso farlo (diciamo che non ho il codice), allora non voglio toccarlo in ogni caso.
Thomas Eding

@MSalters: Nessuna possibilità di riaprire una classe non è solo uno svantaggio, ma anche uno strumento di sicurezza. Perché quando sarebbe possibile riaprire una classe e aggiungere alcuni valori all'enum, potrebbe rompere il codice di altre librerie che già dipende da quell'enum e conosce solo il vecchio insieme di valori. Accetterebbe quindi felicemente quei nuovi valori, ma interromperà in fase di esecuzione dal non sapere cosa farne. Ricorda il principio aperto-chiuso: la classe dovrebbe essere chiusa per la modifica , ma aperta per l' estensione . Per estensione intendo non aggiungere al codice esistente, ma avvolgerlo con il nuovo codice (es. Derivare).
SasQ

Quindi, quando vuoi estendere l'enumerazione, dovresti renderlo un nuovo tipo derivato dal primo (se solo fosse facilmente possibile in C ++ ...; /). Quindi potrebbe essere tranquillamente utilizzato dal nuovo codice che comprende quei nuovi valori, ma solo i vecchi valori sarebbero accettati (convertendoli verso il basso) dal vecchio codice. Non dovrebbero accettare nessuno da quei nuovi valori come di tipo sbagliato (quello esteso). Solo i vecchi valori che capiscono sono accettati come del tipo corretto (base) (e accidentalmente anche del nuovo tipo, quindi potrebbe essere accettato anche dal nuovo codice).
SasQ

7

Il vantaggio di usare una classe è che puoi costruire una classe a tutti gli effetti sopra di essa.

#include <cassert>

class Color
{
public:
    typedef enum
    {
        Red,
        Blue,
        Green,
        Yellow
    } enum_type;

private:
    enum_type _val;

public:
    Color(enum_type val = Blue)
        : _val(val)
    {
        assert(val <= Yellow);
    }

    operator enum_type() const
    {
        return _val;
    }
};

void SetPenColor(const Color c)
{
    switch (c)
    {
        case Color::Red:
            // ...
            break;
    }
}

Come mostra l'esempio sopra, usando una classe puoi:

  1. proibire (purtroppo, non in fase di compilazione) C ++ dal consentire un cast da un valore non valido,
  2. imposta un valore predefinito (diverso da zero) per le enumerazioni appena create,
  3. aggiungere ulteriori metodi, come per restituire una rappresentazione di stringa di una scelta.

Nota solo che devi dichiarare in operator enum_type()modo che C ++ sappia come convertire la tua classe in enum sottostante. In caso contrario, non sarai in grado di passare il tipo a switchun'istruzione.


Questa soluzione è in qualche modo correlata a ciò che viene mostrato qui ?: en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Type_Safe_Enum Sto pensando a come renderlo un modello che non devo riscrivere questo modello ogni volta Ho bisogno di usarlo.
SasQ

@SasQ: sembra simile, sì. Probabilmente è la stessa idea. Tuttavia, non sono sicuro che un modello sia vantaggioso a meno che non si aggiungano molti metodi "comuni".
Michał Górny

1. Non proprio vero. Puoi fare in modo che durante la compilazione verifichi che l'enumerazione sia valida, tramite const_expr o tramite il costruttore privato per un int.
xryl669

5

Poiché le enumerazioni sono limitate al loro ambito di inclusione, è probabilmente meglio racchiuderle in qualcosa per evitare di inquinare lo spazio dei nomi globale e per evitare conflitti di nomi. Preferisco uno spazio dei nomi alla classe semplicemente perché namespacesembra un sacchetto di contenimento, mentre classsembra un oggetto robusto (cfr. Il dibattito structvs. class). Un possibile vantaggio di uno spazio dei nomi è che può essere esteso in un secondo momento, utile se hai a che fare con codice di terze parti che non puoi modificare.

Ovviamente è tutto discutibile quando otteniamo classi enum con C ++ 0x.


enum classes ... devi cercarlo!
xtofl

3

Tendo anche a racchiudere i miei enum nelle classi.

Come segnalato da Richard Corden, il vantaggio di una classe è che è un tipo in senso c ++ e quindi puoi usarlo con i modelli.

Ho una classe speciale toolbox :: Enum per le mie esigenze che sono specializzato per ogni template che fornisce funzioni di base (principalmente: mappare un valore enum a uno std :: string in modo che l'I / O sia più facile da leggere).

Il mio piccolo modello ha anche l'ulteriore vantaggio di verificare realmente i valori consentiti. Il compilatore è un po 'lassista nel controllare se il valore è davvero nell'enumerazione:

typedef enum { False: 0, True: 2 } boolean;
   // The classic enum you don't want to see around your code ;)

int main(int argc, char* argv[])
{
  boolean x = static_cast<boolean>(1);
  return (x == False || x == True) ? 0 : 1;
} // main

Mi ha sempre infastidito il fatto che il compilatore non lo rilevi, dato che ti rimane un valore enum che non ha senso (e che non ti aspetti).

Allo stesso modo:

typedef enum { Zero: 0, One: 1, Two: 2 } example;

int main(int argc, char* argv[])
{
  example y = static_cast<example>(3);
  return (y == Zero || y == One || y == Two) ? 0 : 1;
} // main

Ancora una volta main restituirà un errore.

Il problema è che il compilatore inserirà l'enum nella più piccola rappresentazione disponibile (qui abbiamo bisogno di 2 bit) e che tutto ciò che rientra in questa rappresentazione è considerato un valore valido.

C'è anche il problema che a volte preferiresti avere un ciclo sui valori possibili invece di un interruttore in modo da non dover modificare tutti gli interruttori ogni volta che aggiungi un valore all'enumerazione.

Tutto sommato il mio piccolo aiutante facilita davvero le cose per i miei enumeratori (ovviamente aggiunge un po 'di overhead) ed è possibile solo perché annido ogni enumerazione nella sua struttura :)


4
Interessante. Ti dispiace condividere la definizione della tua classe Enum?
momeara
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.