work - visual c++ enum




Dovrei usare#define, enum o const? (10)

Con definisce perdo la sicurezza del tipo

Non necessariamente...

// signed defines
#define X_NEW      0x01u
#define X_NEW      (unsigned(0x01))  // if you find this more readable...

e con enum perdo uno spazio (interi)

Non necessariamente - ma devi essere esplicito nei punti di archiviazione ...

struct X
{
    RecordType recordType : 4;  // use exactly 4 bits...
    RecordType recordType2 : 4;  // use another 4 bits, typically in the same byte
    // of course, the overall record size may still be padded...
};

e probabilmente devo eseguire il cast quando voglio eseguire operazioni bit a bit.

Puoi creare operatori per eliminare il dolore:

RecordType operator|(RecordType lhs, RecordType rhs)
{
    return RecordType((unsigned)lhs | (unsigned)rhs);
}

Con cost penso di perdere anche la sicurezza del tipo, dal momento che un uint8 casuale potrebbe entrare per errore.

Lo stesso può accadere con uno qualsiasi di questi meccanismi: i controlli di intervallo e valore sono normalmente ortogonali per digitare sicurezza (sebbene i tipi definiti dall'utente, cioè le proprie classi, possano imporre "invarianti" sui loro dati). Con l'enumerazione, il compilatore è libero di scegliere un tipo più grande per ospitare i valori, e una variabile enum non inizializzata, corrotta o appena missata potrebbe ancora finire per interpretare il suo pattern di bit come un numero che non ti aspetteresti - confrontando ineguali con uno qualsiasi gli identificatori di enumerazione, qualsiasi combinazione di essi e 0.

C'è un altro modo più pulito? / Se no, cosa useresti e perché?

Bene, alla fine l'OR bitwise di enumerazioni C-style, collaudato e affidabile, funziona abbastanza bene una volta che i campi bit e gli operatori personalizzati sono presenti nell'immagine. Puoi migliorare ulteriormente la tua robustezza con alcune funzioni di convalida personalizzate e asserzioni come nella risposta di mat_geek; tecniche spesso ugualmente applicabili alla gestione di stringhe, int, doppi valori ecc.

Si potrebbe sostenere che questo è "più pulito":

enum RecordType { New, Deleted, Modified, Existing };

showRecords([](RecordType r) { return r == New || r == Deleted; });

Sono indifferente: i bit di dati sono più rigidi ma il codice cresce in modo significativo ... dipende da quanti oggetti hai e il lamdbas, per quanto sia bello, è ancora più messoso e difficile da ottenere rispetto agli OR bit a bit.

BTW / - l'argomento sull'IMHO piuttosto debole della sicurezza dei thread - meglio ricordato come una considerazione di fondo piuttosto che diventare una forza dominante che guida la decisione; la condivisione di un mutex attraverso i bitfield è una pratica più probabile anche se inconsapevoli del loro packing (i mutex sono membri di dati relativamente voluminosi - devo essere veramente preoccupato delle prestazioni per considerare di avere più mutex sui membri di un oggetto, e guarderei attentamente abbastanza da notare che erano campi di bit). Qualsiasi tipo di sotto-parola-dimensione potrebbe avere lo stesso problema (es. uint8_t ). Ad ogni modo, puoi provare le operazioni di stile di confronto e scambio atomico se sei alla disperata ricerca di una maggiore concorrenza.

In un progetto C ++ su cui sto lavorando, ho un tipo di valore di flag che può avere quattro valori. Quelle quattro bandiere possono essere combinate. Le bandiere descrivono i record nel database e possono essere:

  • nuovo record
  • record cancellato
  • record modificato
  • record esistente

Ora, per ogni record, desidero mantenere questo attributo, quindi potrei usare un enum:

enum { xNew, xDeleted, xModified, xExisting }

Tuttavia, in altri punti del codice, ho bisogno di selezionare quali record devono essere visibili all'utente, quindi mi piacerebbe poterlo passare come un singolo parametro, come:

showRecords(xNew | xDeleted);

Quindi, sembra che io abbia tre possibili appoach:

#define X_NEW      0x01
#define X_DELETED  0x02
#define X_MODIFIED 0x04
#define X_EXISTING 0x08

o

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

o

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

I requisiti di spazio sono importanti (byte vs int) ma non cruciali. Con define perdo la sicurezza di tipo, e con enum perdo uno spazio (interi) e probabilmente devo cast quando voglio fare un'operazione bit a bit. Con const penso di perdere anche la sicurezza del tipo, dal momento che un uint8 casuale potrebbe entrare per errore.

C'è un altro modo più pulito?

In caso contrario, cosa useresti e perché?

PS Il resto del codice è C ++ moderno piuttosto pulito senza #define s, e ho usato spazi dei nomi e modelli in pochi spazi, quindi anche quelli non sono fuori questione.


Dimentica le definizioni

Essi inquineranno il tuo codice.

campi di bit?

struct RecordFlag {
    unsigned isnew:1, isdeleted:1, ismodified:1, isexisting:1;
};

Non usarlo mai . Sei più interessato alla velocità che a risparmiare 4 inte. L'utilizzo dei campi bit è in realtà più lento dell'accesso a qualsiasi altro tipo.

Tuttavia, i membri bit delle strutture hanno degli inconvenienti pratici. Innanzitutto, l'ordine dei bit nella memoria varia dal compilatore al compilatore. Inoltre, molti compilatori popolari generano codice inefficiente per leggere e scrivere membri bit , e ci sono potenzialmente gravi problemi di sicurezza dei thread relativi ai campi di bit (specialmente su sistemi multiprocessore) a causa del fatto che la maggior parte delle macchine non può manipolare insiemi arbitrari di bit in memoria, ma deve invece caricare e memorizzare parole intere. per esempio il seguente non sarebbe sicuro per i thread, nonostante l'uso di un mutex

Fonte: http://en.wikipedia.org/wiki/Bit_field :

E se hai bisogno di più ragioni per non usare bitfield, forse Raymond Chen ti convincerà nel suo The Old New Thing Post: L'analisi costi-benefici dei bitfield per una collezione di booleani su http://blogs.msdn.com/oldnewthing/archive/2008/11/26/9143050.aspx

const int?

namespace RecordType {
    static const uint8 xNew = 1;
    static const uint8 xDeleted = 2;
    static const uint8 xModified = 4;
    static const uint8 xExisting = 8;
}

Metterli in uno spazio dei nomi è bello. Se sono dichiarati nel tuo CPP o nel file di intestazione, i loro valori saranno sottolineati. Sarai in grado di usare questi valori, ma aumenterà leggermente l'accoppiamento.

Ah, sì: rimuovi la parola chiave statica . static è deprecato in C ++ quando usato come fai tu, e se uint8 è un tipo buildin, non avrai bisogno di questo per dichiararlo in un'intestazione inclusa da più fonti dello stesso modulo. Alla fine, il codice dovrebbe essere:

namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Il problema di questo approccio è che il tuo codice conosce il valore delle tue costanti, il che aumenta leggermente l'accoppiamento.

enum

Lo stesso di const int, con una digitazione un po 'più forte.

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Stanno ancora inquinando lo spazio dei nomi globale, però. A proposito ... Rimuovere il typedef . Stai lavorando in C ++. Quei typedef di enumerazioni e strutture stanno inquinando il codice più di ogni altra cosa.

Il risultato è un po ':

enum RecordType { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;

void doSomething(RecordType p_eMyEnum)
{
   if(p_eMyEnum == xNew)
   {
       // etc.
   }
}

Come vedi, il tuo enum sta inquinando lo spazio dei nomi globale. Se metti questo enum in un namespace, avrai qualcosa del tipo:

namespace RecordType {
   enum Value { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } ;
}

void doSomething(RecordType::Value p_eMyEnum)
{
   if(p_eMyEnum == RecordType::xNew)
   {
       // etc.
   }
}

extern const int?

Se vuoi diminuire l'accoppiamento (cioè essere in grado di nascondere i valori delle costanti e, quindi, modificarli come desiderato senza aver bisogno di una ricompilazione completa), puoi dichiarare gli int come extern nell'intestazione e come costante nel file CPP , come nell'esempio seguente:

// Header.hpp
namespace RecordType {
    extern const uint8 xNew ;
    extern const uint8 xDeleted ;
    extern const uint8 xModified ;
    extern const uint8 xExisting ;
}

E:

// Source.hpp
namespace RecordType {
    const uint8 xNew = 1;
    const uint8 xDeleted = 2;
    const uint8 xModified = 4;
    const uint8 xExisting = 8;
}

Non sarai in grado di usare l'accensione di queste costanti, però. Quindi alla fine, scegli il tuo veleno ... :-p


Basato su KISS , alta coesione e basso accoppiamento , ponete queste domande -

  • Chi ha bisogno di sapere? la mia classe, la mia biblioteca, altre classi, altre biblioteche, terze parti
  • Che livello di astrazione devo fornire? Il consumatore capisce le operazioni bit.
  • Avrò bisogno di interfacciare da VB / C # etc?

Esiste un ottimo libro " Progettazione software C ++ su larga scala ", che promuove i tipi di base esternamente, se è possibile evitare un altro file di intestazione / dipendenza dall'interfaccia dovresti provare a farlo.


Combinare le strategie per ridurre gli svantaggi di un singolo approccio. Lavoro in sistemi embedded, quindi la seguente soluzione si basa sul fatto che gli operatori integer e bit a bit sono veloci, con poca memoria e basso utilizzo di flash.

Posiziona l'enum in uno spazio dei nomi per impedire alle costanti di inquinare lo spazio dei nomi globale.

namespace RecordType {

Un enum dichiara e definisce un tempo di compilazione controllato digitato. Usa sempre il controllo del tipo a tempo di compilazione per assicurarti che gli argomenti e le variabili abbiano il tipo corretto. Non è necessario il typedef in C ++.

enum TRecordType { xNew = 1, xDeleted = 2, xModified = 4, xExisting = 8,

Crea un altro membro per uno stato non valido. Questo può essere utile come codice di errore; ad esempio, quando si desidera restituire lo stato ma l'operazione di I / O non riesce. È anche utile per il debug; usalo in liste di inizializzazione e distruttori per sapere se il valore della variabile deve essere usato.

xInvalid = 16 };

Considera che hai due scopi per questo tipo. Per tracciare lo stato corrente di un record e creare una maschera per selezionare i record in determinati stati. Crea una funzione inline per verificare se il valore del tipo è valido per il tuo scopo; come marcatore di stato contro una maschera di stato. Questo catturerà bug poiché typedef è solo un int e un valore come 0xDEADBEEF potrebbe essere nella variabile attraverso variabili non inizializzate o mispointed.

inline bool IsValidState( TRecordType v) {
    switch(v) { case xNew: case xDeleted: case xModified: case xExisting: return true; }
    return false;
}

 inline bool IsValidMask( TRecordType v) {
    return v >= xNew  && v < xInvalid ;
}

Aggiungi una direttiva using se vuoi usare spesso il tipo.

using RecordType ::TRecordType ;

Le funzioni di controllo del valore sono utili nell'asserzione per intercettare valori non validi non appena vengono utilizzati. Quanto più velocemente si riesce a catturare un bug durante l'esecuzione, tanto meno si può danneggiare.

Ecco alcuni esempi per mettere tutto insieme.

void showRecords(TRecordType mask) {
    assert(RecordType::IsValidMask(mask));
    // do stuff;
}

void wombleRecord(TRecord rec, TRecordType state) {
    assert(RecordType::IsValidState(state));
    if (RecordType ::xNew) {
    // ...
} in runtime

TRecordType updateRecord(TRecord rec, TRecordType newstate) {
    assert(RecordType::IsValidState(newstate));
    //...
    if (! access_was_successful) return RecordType ::xInvalid;
    return newstate;
}

L'unico modo per garantire la sicurezza del valore corretto consiste nell'utilizzare una classe dedicata con sovraccarico dell'operatore e che viene lasciata come esercizio per un altro lettore.


Hai escluso std :: bitset? Set di bandiere è ciò che è per. Fare

typedef std::bitset<4> RecordType;

poi

static const RecordType xNew(1);
static const RecordType xDeleted(2);
static const RecordType xModified(4);
static const RecordType xExisting(8);

Perché ci sono un sacco di sovraccarichi dell'operatore per bitset, ora puoi farlo

RecordType rt = whatever;      // unsigned long or RecordType expression
rt |= xNew;                    // set 
rt &= ~xDeleted;               // clear 
if ((rt & xModified) != 0) ... // test

O qualcosa di molto simile a questo - apprezzerei qualsiasi correzione dal momento che non ho provato questo. È anche possibile fare riferimento ai bit per indice, ma generalmente è meglio definire solo un set di costanti e le costanti RecordType sono probabilmente più utili.

Supponendo che tu abbia escluso bitset, voterò per l' enum .

Non comprendo che il casting delle enumerazioni sia un serio svantaggio - OK quindi è un po 'rumoroso, e assegnare un valore fuori campo a un enum è un comportamento indefinito quindi è teoricamente possibile spararsi in piedi su qualche insolito C ++ implementazioni. Ma se lo fai solo quando è necessario (che va da int a enum iirc), è un codice perfettamente normale che le persone hanno visto prima.

Sono dubbioso su qualsiasi costo dello spazio dell'enum. le variabili e i parametri di uint8 probabilmente non useranno meno stack di ints, quindi conta solo lo storage nelle classi. Ci sono alcuni casi in cui vincono più byte in una struct vincono (nel qual caso puoi lanciare enum in e out di uint8), ma normalmente il padding ucciderà comunque il vantaggio.

Quindi l'enumerazione non ha svantaggi rispetto agli altri, e come vantaggio ti dà un po 'di sicurezza del tipo (non puoi assegnare un valore intero casuale senza il casting esplicito) e modi puliti di riferirti a tutto.

Per la preferenza, metterei anche il "= 2" nell'enumerazione, a proposito. Non è necessario, ma un "principio di minimo stupore" suggerisce che tutte e 4 le definizioni debbano essere uguali.


Le enumerazioni sarebbero più appropriate in quanto forniscono "significato agli identificatori" e anche la sicurezza del tipo. Puoi dire chiaramente che "xDeleted" è di "RecordType" e che rappresentano "tipo di record" (wow!) Anche dopo anni. Le azioni richiederebbero commenti per questo, anche loro richiederebbero di andare su e giù nel codice.


Preferirei andare con

typedef enum { xNew = 1, xDeleted, xModified = 4, xExisting = 8 } RecordType;

Semplicemente perchè:

  1. È più pulito e rende il codice leggibile e mantenibile.
  2. Raggruppa logicamente le costanti.
  3. Il tempo del programmatore è più importante, a meno che il tuo lavoro non sia quello di salvare quei 3 byte.

Probabilmente non userei un enum per questo genere di cose in cui i valori possono essere combinati insieme, in genere le enumerazioni sono gli stati che si escludono a vicenda.

Tuttavia, qualunque metodo tu usi, per rendere più chiaro che si tratta di valori che sono bit che possono essere combinati insieme, usa questa sintassi per i valori effettivi:

#define X_NEW      (1 << 0)
#define X_DELETED  (1 << 1)
#define X_MODIFIED (1 << 2)
#define X_EXISTING (1 << 3)

Usando uno spostamento a sinistra ci aiuta a indicare che ogni valore è destinato ad essere un singolo bit, è meno probabile che in seguito qualcuno faccia qualcosa di sbagliato come aggiungere un nuovo valore e assegnargli un valore di 9.


Se possibile, NON utilizzare macro. Non sono troppo ammirati quando si parla di C ++ moderno.


Se si desidera la sicurezza del tipo delle classi, con la comodità della sintassi dell'enumerazione e del controllo dei bit, considerare Safe Labels in C ++ . Ho lavorato con l'autore ed è abbastanza intelligente.

Attenzione, però. Alla fine, questo pacchetto utilizza modelli e macro!







c-preprocessor