Dovrei usare #define, enum o const?


125

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

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

Ora, per ogni record desidero mantenere questo attributo, in modo da poter usare un enum:

enum { xNew, xDeleted, xModified, xExisting }

Tuttavia, in altri punti del codice, devo selezionare quali record devono essere visibili all'utente, quindi vorrei poterlo passare come un singolo parametro, come:

showRecords(xNew | xDeleted);

Quindi, sembra che io abbia tre possibili approcci:

#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 definisce perdo la sicurezza del tipo e con enumperdo un po 'di spazio (numeri interi) e probabilmente devo lanciare quando voglio fare un'operazione bit a bit. Con constpenso di perdere anche la sicurezza dei tipi poiché un casuale uint8potrebbe entrare per errore.

C'è un altro modo più pulito?

In caso contrario, cosa useresti e perché?

PS Il resto del codice è piuttosto pulito C ++ moderno senza #defines, e ho usato spazi dei nomi e modelli in pochi spazi, quindi neanche quelli sono fuori discussione.


"con enum perdo un po 'di spazio (numeri interi)". Non necessariamente. Vedere stackoverflow.com/questions/366017/... e stackoverflow.com/questions/1113855/... (e del GCC -fshort-enum (sto assumendo queste risposte C sono ancora vero in C ++)..
idbrii

@pydave In caso di dubbi sulla compatibilità di C e C ++, trovo questo link molto utile, vedi ad esempio enum david.tribble.com/text/cdiffs.htm#C99-enum-type
aka.nice

3
Questo è un argomento più vecchio con voti alti, c'è un motivo per non menzionare le classi enum C ++ 11 per questa situazione problematica.
Brandin,

Come nota, enum RecordType : uint8_tcombina la sicurezza del tipo di enumcon la piccola dimensione di uint8_t, anche se dovrai comunque fornire operatori bit a bit.
Justin Time - Ripristina Monica l'

Risposte:


88

Combina le strategie per ridurre gli svantaggi di un singolo approccio. Lavoro nei sistemi embedded, quindi la seguente soluzione si basa sul fatto che gli operatori interi e bit a bit sono veloci, con poca memoria e con un uso ridotto del 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 verificato digitato. Utilizzare sempre il controllo del tipo di tempo di compilazione per assicurarsi che gli argomenti e le variabili ricevano 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 I / O non riesce. È anche utile per il debug; usalo negli elenchi di inizializzazione e nei distruttori per sapere se usare il valore della variabile.

xInvalid = 16 };

Considera che hai due scopi per questo tipo. Per tenere traccia dello stato corrente di un record e creare una maschera per selezionare i record in determinati stati. Creare una funzione incorporata per verificare se il valore del tipo è valido per il proprio scopo; come indicatore di stato vs maschera di stato. Questo catturerà i bug poiché typedefè solo un inte un valore come 0xDEADBEEFpotrebbe essere nella tua variabile attraverso variabili non inizializzate o errate.

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 usingdirettiva se desideri utilizzare il tipo spesso.

using RecordType ::TRecordType ;

Le funzioni di controllo del valore sono utili negli asserimenti per intrappolare i valori errati non appena vengono utilizzati. Più rapidamente si rileva un bug durante l'esecuzione, minore è il danno che può fare.

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 corretta sicurezza del valore è utilizzare una classe dedicata con sovraccarichi dell'operatore che viene lasciata come esercizio per un altro lettore.


1
Principalmente una buona risposta, ma la domanda stabilisce che i flag possono essere combinati e la funzione IsValidState () non consente di combinarli.
Jonathan Leffler,

3
@Jonathan Leffler: da dove mi trovo penso che "IsValidState" non dovrebbe farlo, "IsValidMask" lo è.
João Portela,

1
È desiderato che IsValidMasknon consenta di selezionare nessuno (es. 0)?
Joachim Sauer,

2
−1 L'idea del controllo del tipo di runtime è un abominio.
Saluti e hth. - Alf

54

Dimentica i definite

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 all'economizzazione di 4 pollici. L'uso dei campi bit è in realtà più lento dell'accesso a qualsiasi altro tipo.

Tuttavia, i membri di bit nelle strutture presentano inconvenienti pratici. Innanzitutto, l'ordinamento dei bit in memoria varia da compilatore a compilatore. Inoltre, molti compilatori popolari generano un codice inefficiente per la lettura e la scrittura dei membri bit e vi sono problemi di sicurezza dei thread potenzialmente gravi relativi ai campi bit (in particolare sui sistemi multiprocessore) a causa del fatto che la maggior parte delle macchine non è in grado di manipolare insiemi arbitrari di bit in memoria, ma deve invece caricare e memorizzare parole intere. ad esempio, quanto segue 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ù motivi per non usare i bitfield, forse Raymond Chen ti convincerà nel suo The Old New Thing Post: l'analisi costi-benefici dei bitfield per una raccolta 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 file di intestazione, i loro valori saranno in linea. Sarai in grado di utilizzare l'interruttore su 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 ti servirà 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 codice conosce il valore delle costanti, il che aumenta leggermente l'accoppiamento.

enum

Lo stesso di const int, con una digitazione leggermente più forte.

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

Stanno comunque inquinando lo spazio dei nomi globale. A proposito ... Rimuovi il typedef . Stai lavorando in C ++. Quei dattiloscritti 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 questa enum in uno spazio dei nomi, 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 si desidera ridurre l'accoppiamento (ovvero essere in grado di nascondere i valori delle costanti e, quindi, modificarli come desiderato senza necessità di una ricompilazione completa), è possibile dichiarare gli inte come esterni nell'intestazione e come costanti 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;
}

Tuttavia, non sarai in grado di utilizzare l'interruttore su quelle costanti. Quindi alla fine, scegli il tuo veleno ... :-p


5
Perché pensi che i bitfield siano lenti? Hai effettivamente profilato codice utilizzandolo e un altro metodo? Anche se lo è, la chiarezza può essere più importante della velocità, rendendo "non usarlo mai" un po 'semplificato.
wnoise,

"const const uint8 xNew;" è ridondante solo perché in C ++ le variabili con ambito namespace sono predefinite per il collegamento interno. Rimuovi "const" e ha un collegamento esterno. Inoltre, "enum {...} RecordType;" dichiara una variabile globale denominata "RecordType" il cui tipo è un enum anonimo.
bk1e,

onebyone: in primo luogo, il motivo principale è stato che il guadagno (qualche byte, se presente) è stato sovraccaricato dalla perdita (accesso più lento, sia in lettura che in scrittura) ...
paercebal

3
onebyone: secondo, tutto il codice che produco al lavoro o a casa è intrinsecamente sicuro per i thread. È facile da fare: niente globi, niente statici, non condivisi tra thread a meno che non siano protetti da blocco. L'uso di questo idioma spezzerebbe questa sicurezza di base del thread. E per cosa? Qualche byte forse ? ... :-) ...
paercebal,

Aggiunto riferimento all'articolo di Raymond Chen sui costi nascosti dei bitfield.
paercebal,

30

Hai escluso std :: bitset? Gli insiemi di bandiere servono a questo scopo. 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 il 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 poiché non l'ho testato. Puoi anche fare riferimento ai bit per indice, ma in genere è meglio definire solo un set di costanti e le costanti RecordType sono probabilmente più utili.

Supponendo che tu abbia escluso il bitset, voto per l' enum .

Non compro che lanciare gli enum sia un grave svantaggio - OK, quindi è un po 'rumoroso e assegnare un valore fuori portata a un enum è un comportamento indefinito, quindi teoricamente è possibile spararsi ai piedi con un insolito C ++ implementazioni. Ma se lo fai solo quando necessario (che è quando vai da int a enum iirc), è un codice perfettamente normale che le persone hanno visto prima.

Sono anche incerto su qualsiasi costo di spazio dell'enum. Le variabili e i parametri uint8 probabilmente non useranno meno stack di ints, quindi conta solo l'archiviazione nelle classi. Ci sono alcuni casi in cui vincerà il confezionamento di più byte in una struttura (nel qual caso è possibile eseguire il cast di enumerazioni dentro e fuori dalla memoria di uint8), ma normalmente il riempimento ucciderà comunque il vantaggio.

Quindi l'enum 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 esplicitamente il cast) e modi chiari di fare riferimento a tutto.

A proposito, metterei anche "= 2" nell'enum, comunque. Non è necessario, ma un "principio di minimo stupore" suggerisce che tutte e 4 le definizioni dovrebbero apparire uguali.


1
In realtà, non ho considerato affatto il bitset. Tuttavia, non sono sicuro che sarebbe buono. Con bitset, devo indirizzare i bit come 1, 2, 3, 4 che renderebbero il codice meno leggibile, il che significa che probabilmente userò un enum per "nominare" i bit. Potrebbe essere un risparmio di spazio però. Grazie.
Milano Babuškov,

Milano, non devi "nominare" i bit usando un enum, puoi semplicemente usare i bit predefiniti come mostrato sopra. Se vuoi attivare il bit 1, anziché my_bitset.flip (1), dovresti fare my_bitset | = xNew;
moswald,

questo è diretto meno a te e di più alla STL, ma: devo davvero chiederti: perché dovresti usare bitsetper questo? di solito si traduce in un longtipo integrale (nella mia implementazione; sì, quanto dispendioso) o simile per ogni elemento, quindi perché non usare solo integrali non offuscati? (o, al giorno d'oggi, constexprcon zero spazio di archiviazione)
underscore_d

[modifica timeout] ... ma poi non ho mai veramente capito la logica della bitsetclasse, a parte quella che sembra essere una corrente periodica ricorrente nelle discussioni circostanti su 'ugh, dobbiamo nascondere le sgradevoli radici di basso livello del linguaggio '
underscore_d

"le uint8variabili e i parametri probabilmente non useranno meno stack di ints" è sbagliato. Se disponi di una CPU con registri a 8 bit, sono intnecessari almeno 2 registri mentre ne uint8_toccorre solo 1, quindi avrai bisogno di più spazio nello stack perché è più probabile che tu sia fuori dai registri (che è anche più lento e può aumentare la dimensione del codice ( a seconda delle istruzioni)). (Si dispone di un tipo, dovrebbe essere uint8_tnon uint8)
12431234123412341234123


5

Se possibile NON utilizzare le macro. Non sono troppo ammirati quando si tratta del C ++ moderno.


4
Vero. Quello che odio delle macro me stesso è che non puoi intervenire se si sbagliano.
Carl,

Immagino che sia qualcosa che potrebbe essere corretto nel compilatore.
celticminstrel,

4

Gli enum sarebbero più appropriati in quanto forniscono "significato agli identificatori" e sicurezza del tipo. Puoi dire chiaramente che "xDeleted" è di "RecordType" e che rappresenta il "tipo di record" (wow!) Anche dopo anni. I costi richiederebbero commenti per questo, inoltre richiederebbero di andare su e giù nel codice.


4

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 un po 'di spazio (numeri 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 lanciare quando voglio fare un'operazione bit a bit.

È possibile creare operatori per risolvere il problema:

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

Con const penso di perdere anche la sicurezza dei tipi poiché un uint8 casuale potrebbe entrare per errore.

Lo stesso può accadere con uno di questi meccanismi: i controlli di intervallo e valore sono normalmente ortogonali alla sicurezza dei tipi (sebbene i tipi definiti dall'utente - cioè le tue classi - possano imporre "invarianti" sui loro dati). Con enums, il compilatore è libero di scegliere un tipo più grande per ospitare i valori e una variabile enum non inizializzata, corrotta o semplicemente errata potrebbe finire per interpretare il suo modello di bit come un numero che non ti aspetteresti - confrontando ineguale 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 il comprovato OR bit-down di enumerazioni in stile C funziona abbastanza bene una volta che hai campi bit e operatori personalizzati nella foto. Puoi migliorare ulteriormente la tua solidità con alcune funzioni e asserzioni personalizzate di convalida 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ù stretti ma il codice cresce in modo significativo ... dipende da quanti oggetti hai, e i lamdbas - per quanto siano belli - sono ancora più difficili e difficili da ottenere rispetto agli OR bit a bit.

A proposito / - l'argomento sull'IMHO piuttosto debole della sicurezza dei thread - è meglio ricordarlo come considerazione di fondo piuttosto che diventare una forza trainante decisionale dominante; condividere un mutex attraverso i bitfield è una pratica più probabile anche se ignari del loro impacchettamento (i mutex sono membri di dati relativamente voluminosi - devo essere davvero preoccupato delle prestazioni per considerare di avere più mutex sui membri di un oggetto, e guarderei attentamente abbastanza da notare che erano piccoli campi). Qualsiasi tipo di dimensione di una parola secondaria potrebbe avere lo stesso problema (ad es uint8_t. A). Ad ogni modo, potresti provare operazioni atomiche di confronto e scambio se sei alla disperata ricerca di una maggiore concorrenza.


1
+1 Bene. Ma il operator|cast dovrebbe essere un tipo intero ( unsigned int) prima dell'istruzione |. Altrimenti la operator|volontà si chiamerà in modo ricorsivo e causerà un overflow dello stack di runtime. Suggerisco: return RecordType( unsigned(lhs) | unsigned(rhs) );. Saluti
olibre

3

Anche se devi usare 4 byte per memorizzare un enum (non ho molta familiarità con C ++ - so che puoi specificare il tipo sottostante in C #), ne vale comunque la pena - usa enums.

Al giorno d'oggi dei server con GB di memoria, cose come 4 byte contro 1 byte di memoria a livello di applicazione in generale non contano. Naturalmente, se nella tua situazione particolare, l'utilizzo della memoria è così importante (e non riesci a far sì che C ++ usi un byte per sostenere l'enum), allora puoi considerare il percorso 'const statico'.

Alla fine della giornata, devi chiederti, vale la pena il colpo di manutenzione dell'utilizzo di 'const statica' per i 3 byte di risparmio di memoria per la tua struttura dati?

Qualcos'altro da tenere a mente - IIRC, su x86, le strutture di dati sono allineate a 4 byte, quindi a meno che tu non abbia un numero di elementi di larghezza di byte nella tua struttura 'record', potrebbe non avere importanza. Prova e assicurati che lo faccia prima di fare un compromesso sulla manutenibilità per prestazioni / spazio.


È possibile specificare il tipo sottostante in C ++, a partire dalla revisione del linguaggio C ++ 11. Fino ad allora, credo che fosse "almeno abbastanza grande per essere archiviato e utilizzato come campo bit per tutti gli enumeratori specificati, ma probabilmente a intmeno che non sia troppo piccolo". [Se non si specifica il tipo sottostante in C ++ 11, utilizza un comportamento legacy. Al contrario, il enum classtipo di base di C ++ 11 intviene impostato in modo esplicito su valori predefiniti se non diversamente specificato.]
Justin Time - Ripristina Monica

3

Se si desidera il tipo di sicurezza delle classi, con la comodità della sintassi di enumerazione e del controllo dei bit, prendere in considerazione le etichette sicure in C ++ . Ho lavorato con l'autore ed è piuttosto intelligente.

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


Sembra eccessivo per la mia piccola app. ma sembra una buona soluzione.
Milan Babuškov,

2

Hai davvero bisogno di passare intorno ai valori della bandiera come un insieme concettuale o avrai un sacco di codice per bandiera? Ad ogni modo, penso che avere questo come classe o struttura di bitfield a 1 bit potrebbe effettivamente essere più chiaro:

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

Quindi la tua classe di record potrebbe avere una variabile membro RecordFlag struct, le funzioni possono prendere argomenti di tipo struct RecordFlag, ecc. Il compilatore dovrebbe mettere insieme i bitfield, risparmiando spazio.


A volte nel suo insieme, a volte come bandiera. Inoltre, devo anche verificare se è impostato un determinato flag (quando lo passo nel suo insieme).
Milano Babuškov,

bene, quando separato, basta chiedere un int. Quando insieme, passa la struttura.
wnoise,

Non andrà meglio. L'accesso ai campi di bit è più lento di ogni altra cosa.
paercebal,

Veramente? Pensi che il compilatore genererà un codice significativamente diverso per testare i bit-field rispetto al bit-twiddling manuale? E che sarà significativamente più lento? Perché? L'unica cosa che non puoi fare così facilmente idiomaticamente è mascherare più flag contemporaneamente.
wnoise,

Eseguendo un semplice test di lettura ottengo 5,50-5,58 secondi per il mascheramento di bit rispetto a 5,45-5,59 per l'accesso al campo di bit. Praticamente indistinguibile.
wnoise,

2

Probabilmente non userei un enum per questo genere di cose in cui i valori possono essere combinati insieme, più tipicamente gli enum sono stati reciprocamente esclusivi.

Ma qualunque metodo tu usi, per rendere più chiaro che si tratta di valori che sono bit che possono essere combinati insieme, usa invece 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 aiuta a indicare che ogni valore è inteso per 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.


1
C'è abbastanza precedente per questo, in particolare nelle costanti di ioctl (). Preferisco usare le costanti esadecimali, però: 0x01, 0x02, 0x04, 0x08, 0x10, ...
Jonathan Leffler

2

Basato su KISS , elevata coesione e basso accoppiamento , poni queste domande:

  • Chi ha bisogno di sapere? la mia classe, la mia biblioteca, altre classi, altre biblioteche, terze parti
  • Quale livello di astrazione devo fornire? Il consumatore comprende le operazioni a bit?
  • Dovrò interfacciarmi da VB / C # ecc.?

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


1
a) 5-6 classi. b) solo io, è un progetto individuale c) nessuna interfaccia
Milan Babuškov,

2

Se stai usando Qt dovresti dare un'occhiata a QFlags . La classe QFlags fornisce un modo sicuro di tipo per memorizzare combinazioni OR di valori enum.


No, nessun Qt. In realtà, è un progetto wxWidgets.
Milan Babuškov,

0

Preferirei andare con

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

Semplicemente perchè:

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

Bene, potrei facilmente avere un milione di istanze della classe Record, quindi potrebbe essere importante. OTOH, questa è solo una differenza tra 1 MB e 4 MB, quindi forse non dovrei preoccuparmi.
Milano Babuškov,

@Vivek: hai considerato la limitazione della larghezza intera? In particolare prima di C ++ 11.
user2672165

0

Non che mi piace progettare troppo tutto, ma a volte in questi casi può valere la pena creare una (piccola) classe per incapsulare queste informazioni. Se si crea una classe RecordType, potrebbe avere funzioni come:

void setDeleted ();

void clearDeleted ();

bool isDeleted ();

ecc ... (o qualsiasi altra convenzione adatta)

Potrebbe convalidare le combinazioni (nel caso in cui non tutte le combinazioni siano legali, ad esempio se "nuovo" e "eliminato" non possono essere impostati contemporaneamente). Se hai appena usato maschere di bit, ecc., Il codice che imposta lo stato deve essere convalidato, anche una classe può incapsulare quella logica.

La classe può anche darti la possibilità di allegare informazioni di registrazione significative a ciascuno stato, puoi aggiungere una funzione per restituire una rappresentazione in forma di stringa dello stato corrente ecc. (O usare gli operatori di streaming '<<').

Nonostante ciò, se sei preoccupato per l'archiviazione, potresti comunque avere la classe con solo un membro di dati 'char', quindi prendi solo una piccola quantità di spazio di archiviazione (supponendo che non sia virtuale). Naturalmente a seconda dell'hardware ecc. Potresti avere problemi di allineamento.

È possibile che i valori dei bit effettivi non siano visibili al resto del "mondo" se si trovano in uno spazio dei nomi anonimo all'interno del file cpp anziché nel file di intestazione.

Se scopri che il codice che usa l'enum / # define / bitmask ecc ha un sacco di codice 'support' per gestire combinazioni non valide, registrazione ecc., Allora vale la pena considerare l'incapsulamento in una classe. Naturalmente la maggior parte delle volte i problemi semplici sono migliori con soluzioni semplici ...


Sfortunatamente, la dichiarazione deve essere in un file .h poiché viene utilizzata in tutto il progetto (utilizzato da alcune classi 5-6).
Milano Babuškov,
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.