const statica vs #define


212

È meglio usare static constVars rispetto al #definepreprocessore? O forse dipende dal contesto?

Quali sono i vantaggi / gli svantaggi per ciascun metodo?


14
Scott Meyers tratta questo argomento in modo molto accurato e completo. Il suo articolo n. 2 in "Effective C ++ Third Edition". Due casi speciali (1) const statici sono preferiti all'interno di un ambito di classe per costanti specifiche di classe; (2) lo spazio dei nomi o la const anonima dell'ambito sono preferiti rispetto a #define.
Eric,

2
Preferisco Enums. Perché è ibrido di entrambi. Non occupa spazio se non ne crei una variabile. Se vuoi solo usare come costante, enum è l'opzione migliore. Ha una sicurezza di tipo in C / C ++ 11 std e anche una costante perfetta. #define è di tipo non sicuro, const occupa spazio se il compilatore non è in grado di ottimizzarlo.
siddhusingh,

1
La mia decisione se usare #defineo static const(per le stringhe) è guidata dall'aspetto di inizializzazione (non è stato menzionato nelle risposte seguenti): se la costante viene utilizzata solo all'interno di una particolare unità di compilazione, allora vado con static const, altrimenti uso #define- evita l'inizializzazione dell'ordine statico fiasco isocpp.org/wiki/faq/ctors#static-init-order
Martin Dvorak

Se const, constexpro enumo qualsiasi variante funziona nel tuo caso, preferiscilo a#define
Phil1970,

@MartinDvorak " evitare il fiasco di inizializzazione dell'ordine statico " Come è un problema per le costanti?
curiousguy,

Risposte:


139

Personalmente detesto il preprocessore, quindi ci andrei sempre const.

Il vantaggio principale di a #defineè che non richiede memoria per l'archiviazione nel programma, in quanto sostituisce semplicemente del testo con un valore letterale. Ha anche il vantaggio di non avere alcun tipo, quindi può essere utilizzato per qualsiasi valore intero senza generare avvisi.

I vantaggi di " const" s sono che possono essere definiti e possono essere utilizzati in situazioni in cui è necessario passare un puntatore a un oggetto.

staticTuttavia, non so esattamente a cosa stai arrivando con la parte " ". Se stai dichiarando a livello globale, lo metterei in uno spazio dei nomi anonimo invece di utilizzare static. Per esempio

namespace {
   unsigned const seconds_per_minute = 60;
};

int main (int argc; char *argv[]) {
...
}

8
Le costanti di stringa in particolare sono tra quelle che potrebbero trarre vantaggio dall'essere #defined, almeno se possono essere utilizzate come "blocchi" per costanti di stringa più grandi. Vedi la mia risposta per un esempio.
AnT

62
Il #definevantaggio di non utilizzare memoria è inaccurato. Il "60" nell'esempio deve essere memorizzato da qualche parte, indipendentemente dal fatto che sia static consto #define. In effetti, ho visto compilatori in cui l'uso di #define ha causato un consumo massiccio di memoria (sola lettura) e la const statica non ha utilizzato memoria non necessaria.
Gilad Naor,

3
Un #define è come se lo avessi digitato, quindi sicuramente non viene dalla memoria.
il reverendo

27
@theReverend I valori letterali sono in qualche modo esenti dal consumo di risorse della macchina? No, potrebbero semplicemente usarli in modi diversi, forse non apparirà nello stack o nell'heap, ma ad un certo punto il programma viene caricato in memoria insieme a tutti i valori compilati in esso.
Sqeaky

13
@ gilad-naor, in generale hai ragione, ma i piccoli numeri interi come 60 possono in realtà essere una sorta di eccezione parziale. Alcuni set di istruzioni hanno la capacità di codificare numeri interi o un sottoinsieme di numeri interi direttamente nel flusso di istruzioni. Ad esempio, i MIP aggiungono immediati ( cs.umd.edu/class/sum2003/cmsc311/Notes/Mips/addi.html ). In questo tipo di caso, si potrebbe dire che un numero intero # definito non utilizza spazio poiché nel binario compilato occupa alcuni bit di riserva nelle istruzioni che dovevano esistere comunque.
ahcox,

242

Pro e contro tra #defines, se const(cosa hai dimenticato) enum, a seconda dell'uso:

  1. enumS:

    • possibile solo per valori interi
    • problemi di conflitto correttamente identificati / identificativi gestiti in modo corretto, in particolare nelle classi enum C ++ 11 in cui sono elencate le enumerazioni enum class X sono ambigue dall'ambitoX::
    • fortemente tipizzato, ma con una dimensione int con o senza segno sufficientemente grande su cui non si ha alcun controllo in C ++ 03 (sebbene sia possibile specificare un campo di bit in cui devono essere compressi se l'enum è un membro di struct / class / union), mentre per impostazione predefinita C ++ 11 è int viene impostato in modo esplicito dal programmatore
    • non può prendere l'indirizzo - non ce n'è uno poiché i valori di enumerazione sono effettivamente sostituiti in linea nei punti di utilizzo
    • restrizioni d'uso più forti (ad es. incremento - template <typename T> void f(T t) { cout << ++t; } non verranno compilati, sebbene sia possibile racchiudere un enum in una classe con costruttore implicito, operatore di casting e operatori definiti dall'utente)
    • ogni tipo di costante preso dall'enum che lo racchiude, quindi template <typename T> void f(T) ottiene un'istanza distinta quando viene passato lo stesso valore numerico da enumerazioni diverse, tutte distinte da qualsiasi f(int)istanza effettiva . Il codice oggetto di ciascuna funzione potrebbe essere identico (ignorando gli offset degli indirizzi), ma non mi aspetto che un compilatore / linker elimini le copie non necessarie, anche se potresti controllare il compilatore / linker se ti interessa.
    • anche con typeof / decltype, non può aspettarsi che numeric_limits fornisca informazioni utili sull'insieme di valori e combinazioni significative (in effetti, le combinazioni "legali" non sono nemmeno annotate nel codice sorgente, considera enum { A = 1, B = 2 }- èA|B "legale" da una logica di programma prospettiva?)
    • il nome tipografico dell'enum può apparire in vari punti in RTTI, messaggi del compilatore ecc. - Forse utile, forse offuscato
    • non è possibile utilizzare un'enumerazione senza che l'unità di traduzione visualizzi effettivamente il valore, il che significa che gli enum nelle API della libreria necessitano dei valori esposti nell'intestazione makee altri strumenti di ricompilazione basati su data / ora attiveranno la ricompilazione del client quando vengono cambiati (male! )

  1. constS:

    • problemi di conflitto correttamente identificati / identificativi gestiti correttamente
    • tipo forte, singolo, specificato dall'utente
      • potresti provare a "digitare" #defineun'ala #define S std::string("abc"), ma la costante evita la ripetuta costruzione di temporali distinti in ogni punto di utilizzo
    • Una definizione Complicanze della regola
    • può prendere l'indirizzo, creare riferimenti costanti a loro ecc.
    • più simile a un non-const valore, che riduce al minimo il lavoro e l'impatto se si passa
    • il valore può essere inserito nel file di implementazione, consentendo una ricompilazione localizzata e solo i collegamenti client per raccogliere la modifica

  1. #defineS:

    • ambito "globale" / più soggetto a usi contrastanti, che possono produrre problemi di compilazione difficili da risolvere e risultati di runtime imprevisti piuttosto che messaggi di errore sani di mente; mitigare ciò richiede:
      • identificatori lunghi, oscuri e / o coordinati centralmente e l'accesso ad essi non può beneficiare della corrispondenza implicita dello spazio dei nomi usato / corrente / cercato Koenig, degli alias dello spazio dei nomi, ecc.
      • mentre la best practice di trump consente agli identificatori di parametri di modello di essere lettere maiuscole a carattere singolo (eventualmente seguite da un numero), altri usi di identificatori senza lettere minuscole sono convenzionalmente riservati e previsti dai definitori del preprocessore (al di fuori delle librerie OS e C / C ++ header). Ciò è importante affinché l'utilizzo del preprocessore su scala aziendale rimanga gestibile. È possibile che le librerie di terze parti siano conformi. L'osservazione di questo implica la migrazione di cons o di enumerazioni esistenti da / verso le definizioni comporta un cambiamento di capitalizzazione e quindi richiede modifiche al codice sorgente del client piuttosto che una "semplice" ricompilazione. (Personalmente, scrivo in maiuscolo la prima lettera di enumerazioni ma non i contro, quindi sarei colpito anche migrando tra quei due - forse è il momento di ripensarlo.)
    • sono possibili più operazioni in fase di compilazione: concatenazione letterale di stringhe, stringificazioni (prendendo le loro dimensioni), concatenazione in identificatori
      • il rovescio della medaglia è quello dato #define X "x"e un po 'di utilizzo del client "pre" X "post", se si desidera o è necessario rendere X una variabile modificabile in fase di esecuzione anziché una costante, si impongono le modifiche al codice client (anziché solo la ricompilazione), mentre tale transizione è più facile da const char*o const std::stringdata già costringe l'utente a incorporare operazioni di concatenazione (ad es. "pre" + X + "post"per string)
    • non può essere utilizzato sizeofdirettamente su un valore letterale numerico definito
    • non tipizzato (GCC non avvisa se confrontato con unsigned)
    • alcune catene di compilatori / linker / debugger potrebbero non presentare l'identificatore, quindi sarai ridotto a guardare "numeri magici" (stringhe, qualunque cosa ...)
    • non posso prendere l'indirizzo
    • il valore sostituito non deve essere legale (o discreto) nel contesto in cui viene creato #define, poiché viene valutato in ciascun punto di utilizzo, quindi è possibile fare riferimento a oggetti non ancora dichiarati, a seconda dell '"implementazione" che non necessita essere pre-incluso, creare "costanti" come quelle { 1, 2 }che possono essere utilizzate per inizializzare array o #define MICROSECONDS *1E-6ecc. ( sicuramente non lo consiglio!)
    • alcune cose speciali come __FILE__e__LINE__ possono essere incorporate nella sostituzione macro
    • è possibile verificare l'esistenza e il valore nelle #ifistruzioni per includere condizionalmente il codice (più potente di un post-preelaborazione "if" poiché il codice non deve essere compilabile se non selezionato dal preprocessore), utilizzare#undef -ine, ridefinisci ecc.
    • il testo sostituito deve essere esposto:
      • nell'unità di traduzione utilizzata, il che significa che le macro nelle librerie per l'uso client devono essere nell'intestazione, quindi makee altri strumenti di ricompilazione basati su data / ora attiveranno la ricompilazione del client quando vengono cambiati (male!)
      • o dalla riga di comando, dove è necessaria ancora più attenzione per assicurarsi che il codice client sia ricompilato (ad es. Makefile o script che fornisce la definizione devono essere elencati come una dipendenza)

La mia opinione personale:

Come regola generale, uso consts e li considero l'opzione più professionale per un utilizzo generale (sebbene gli altri abbiano una semplicità che fa appello a questo vecchio programmatore pigro).


1
Risposta fantastica. Un piccolo inconveniente: a volte uso enumerazioni locali che non sono affatto nelle intestazioni solo per chiarezza del codice, come nelle piccole macchine a stati e simili. Quindi non devono essere sempre nelle intestazioni.
kert,

Pro e contro sono confusi, mi piacerebbe molto vedere una tabella di confronto.
Sconosciuto123

@ Unknown123: sentiti libero di pubblicarne uno - non mi dispiace se ti freghi qualche punto che ritieni degno da qui. Saluti
Tony Delroy,

48

Se questa è una domanda C ++ e menziona #definecome alternativa, allora si tratta di costanti "globali" (cioè di ambito di file), non di membri della classe. Quando si tratta di tali costanti in C ++ static constè ridondante. In C ++ consthanno un collegamento interno di default e non ha senso dichiararli static. Quindi è veramente constcontro #define.

E, infine, in C ++ constè preferibile. Almeno perché tali costanti sono tipizzate e con ambito. Semplicemente non ci sono ragioni per preferire #defineoltre a constpoche eccezioni.

Le costanti di stringa, BTW, sono un esempio di tale eccezione. Con le #definecostanti stringa d si può usare la funzione di concatenazione in fase di compilazione dei compilatori C / C ++, come in

#define OUT_NAME "output"
#define LOG_EXT ".log"
#define TEXT_EXT ".txt"

const char *const log_file_name = OUT_NAME LOG_EXT;
const char *const text_file_name = OUT_NAME TEXT_EXT;

PS Ancora una volta, nel caso, quando qualcuno menziona static constun'alternativa a #define, di solito significa che stanno parlando di C, non di C ++. Mi chiedo se questa domanda sia taggata correttamente ...


1
" semplicemente nessun motivo per preferire #define " Su cosa? Variabili statiche definite in un file di intestazione?
curiousguy,

9

#define può portare a risultati imprevisti:

#include <iostream>

#define x 500
#define y x + 5

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Emette un risultato errato:

y is 505
z is 510

Tuttavia, se lo sostituisci con costanti:

#include <iostream>

const int x = 500;
const int y = x + 5;

int z = y * 2;

int main()
{
    std::cout << "y is " << y;
    std::cout << "\nz is " << z;
}

Produce il risultato corretto:

y is 505
z is 1010

Questo perché #definesemplicemente sostituisce il testo. Dato che ciò può compromettere seriamente l'ordine delle operazioni, raccomanderei invece di utilizzare una variabile costante.


1
Ho avuto un diverso risultato inaspettato: yaveva il valore 5500, una concatenazione little-endian di xe 5.
Codici con Hammer

5

Usare una const statica è come usare qualsiasi altra variabile const nel tuo codice. Questo significa che puoi tracciare da dove provengono le informazioni, al contrario di un #define che sarà semplicemente sostituito nel codice nel processo di pre-compilazione.

Potresti dare un'occhiata alle Domande frequenti Lite C ++ per questa domanda: http://www.parashift.com/c++-faq-lite/newbie.html#faq-29.7


4
  • Viene digitata una const statica (ha un tipo) e può essere verificata dal compilatore per validità, ridefinizione ecc.
  • un #define può essere ridefinito indefinito qualunque cosa.

Di solito dovresti preferire i contro statici. Non ha svantaggi. Il processore dovrebbe essere utilizzato principalmente per la compilazione condizionale (e talvolta per trucchi davvero sporchi forse).


3

#defineNon è consigliabile definire costanti utilizzando la direttiva preprocessore da applicare non solo in C++, ma anche in C. Queste costanti non avranno il tipo. Anche in è Cstato proposto di utilizzare constper le costanti.



2

Preferisci sempre utilizzare le funzionalità del linguaggio su alcuni strumenti aggiuntivi come il preprocessore.

ES.31: Non utilizzare le macro per costanti o "funzioni"

Le macro sono una delle principali fonti di bug. Le macro non obbediscono alle normali regole di ambito e tipo. Le macro non obbediscono alle solite regole per il passaggio degli argomenti. Le macro assicurano che il lettore umano veda qualcosa di diverso da ciò che vede il compilatore. Le macro complicano la costruzione degli strumenti.

Dalle linee guida di base C ++


0

Se stai definendo una costante da condividere tra tutte le istanze della classe, usa const statica. Se la costante è specifica per ogni istanza, basta usare const (ma si noti che tutti i costruttori della classe devono inizializzare questa variabile del membro const nell'elenco di inizializzazione).

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.