Uso di 'const' per i parametri delle funzioni


397

Quanto vai lontano const? Fai semplicemente funzioniconst quando necessario o fai tutto il maiale e lo usi ovunque? Ad esempio, immagina un semplice mutatore che accetta un singolo parametro booleano:

void SetValue(const bool b) { my_val_ = b; }

È questo const davvero utile? Personalmente scelgo di usarlo ampiamente, compresi i parametri, ma in questo caso mi chiedo se valga la pena?

Sono stato anche sorpreso di apprendere che puoi omettere const parametri in una dichiarazione di funzione ma includerla nella definizione della funzione, ad esempio:

.h file

void func(int n, long l);

file .cpp

void func(const int n, const long l)

C'è una ragione per questo? Mi sembra un po 'insolito.


Non sono d'accordo. Il file .h deve avere anche le definizioni const. In caso contrario, se i parametri const vengono passati alla funzione, il compilatore genererà un errore, poiché il prototipo nel file .h non ha le definizioni const.
selwyn,

10
Sono d'accordo. :-) (Con la domanda, non l'ultimo commento!) Se un valore non deve essere cambiato nel corpo della funzione, questo può aiutare a fermare gli stupidi bug == o =, non dovresti mai mettere const in entrambi, (se è passato per valore, è necessario altrimenti) Non è abbastanza serio entrare in discussioni su questo!
Chris Huang-Leaver,

19
@selwyn: Anche se passi una const int alla funzione, tuttavia, verrà copiata (dal momento che non è un riferimento), e quindi la costanza non ha importanza.
jalf

1
Stesso dibattito in corso in questa domanda: stackoverflow.com/questions/1554750/…
Parziale

5
Mi rendo conto che questo post ha un paio d'anni, ma come nuovo programmatore, mi chiedevo proprio questa domanda e mi sono imbattuto in questa conversazione. Secondo me, se una funzione non dovesse cambiare un valore, sia esso un riferimento o una copia del valore / oggetto, dovrebbe essere const. È più sicuro, è auto-documentante ed è più facile da eseguire per il debug. Anche per la funzione più semplice, che ha un'istruzione, uso ancora const.

Risposte:


187

Il motivo è che const per il parametro si applica solo localmente all'interno della funzione, poiché sta lavorando su una copia dei dati. Ciò significa che la firma della funzione è comunque sempre la stessa. Probabilmente è un brutto stile farlo molto però.

Personalmente tendo a non usare const se non per i parametri di riferimento e puntatore. Per gli oggetti copiati non importa, anche se può essere più sicuro in quanto segnala l'intento all'interno della funzione. È davvero un giudizio. Tendo a usare const_iterator durante il ciclo su qualcosa e non intendo modificarlo, quindi immagino a ciascuno il suo, purché la correttezza const per i tipi di riferimento sia rigorosamente mantenuta.


57
Non posso essere d'accordo con la parte "cattivo stile". Il rilascio constda prototipi di funzioni ha il vantaggio di non dover modificare il file di intestazione se si decide di abbandonare constsuccessivamente la parte di implementazione.
Michał Górny,

4
"Personalmente tendo a non usare const se non per i parametri di riferimento e puntatore." Forse dovresti chiarire che a "Tendo a non usare qualificatori superflui nelle dichiarazioni di funzioni, ma usare constdove fa una differenza utile".
Deduplicatore,

3
Non sono d'accordo con questa risposta. Mi sposto dall'altra parte e segnare i parametri constogni volta che è possibile; è più espressivo. Quando leggo il codice di qualcun altro, uso piccoli indicatori come questo per giudicare quanta cura mettono nello scrivere il loro codice insieme a cose come numeri magici, commenti e uso corretto del puntatore, ecc.
Ultimater

4
int getDouble(int a){ ++a; return 2*a; }Prova questo. Naturalmente, ++anon ha nulla a che fare lì, ma può essere trovato lì in una lunga funzione scritta da più di un programmatore per un lungo periodo di tempo. Consiglio vivamente di scrivere int getDouble( const int a ){ //... }che genererà un errore di compilazione durante la ricerca ++a;.
dom_beau

3
Dipende tutto da chi ha bisogno di quali informazioni. Fornisci il parametro in base al valore, quindi il chiamante non ha bisogno di sapere nulla di ciò che fai (internamente) con esso. Quindi scrivi class Foo { int multiply(int a, int b) const; }nella tua intestazione. Nella tua implementazione ti preoccupi di poter promettere di non alterare ae bquindi int Foo::multiply(const int a, const int b) const { }ha senso qui. (Sidenote: sia il chiamante che l'implementazione si preoccupano del fatto che la funzione non alteri il suo Foooggetto, quindi la const alla fine della sua dichiarazione)
CharonX

415

"const è inutile quando l'argomento viene passato per valore poiché non modificherai l'oggetto del chiamante."

Sbagliato.

Si tratta di auto-documentare il tuo codice e i tuoi presupposti.

Se il tuo codice ha molte persone che ci lavorano e le tue funzioni non sono banali, allora dovresti contrassegnare "const" qualsiasi cosa tu possa fare. Quando scrivi un codice di forza industriale, dovresti sempre presumere che i tuoi colleghi siano psicopatici che cercano di farti in ogni modo possibile (soprattutto perché spesso è te stesso in futuro).

Inoltre, come qualcuno ha detto in precedenza, potrebbe aiutare il compilatore a ottimizzare un po 'le cose (anche se è un colpo lungo).


41
Completamente d'accordo. Si tratta di comunicare con le persone e di limitare ciò che si potrebbe fare con una variabile a ciò che dovrebbe essere fatto.
Len Holgate,

19
Ho votato questo verso il basso. Penso che diluisci ciò che stai cercando di indicare con const quando lo applichi a semplici argomenti pass by value.
tonylo,

26
Ho votato a favore. La dichiarazione del parametro 'const' aggiunge informazioni semantiche al parametro. Evidenziano ciò che l'autore originale del codice aveva inteso e questo aiuterà il mantenimento del codice col passare del tempo.
Richard Corden,

13
@tonylo: fraintendete. Si tratta di contrassegnare una variabile locale come const all'interno di un blocco di codice (che sembra essere una funzione). Farei lo stesso per qualsiasi variabile locale. È ortogonale avere un'API che sia corretta, il che è davvero importante.
Rlerallut,

56
E può catturare i bug all'interno della funzione - se sai che un parametro non dovrebbe essere cambiato, quindi dichiararlo const significa che il compilatore ti dirà se lo modifichi accidentalmente.
Adrian,

156

A volte (troppo spesso!) Devo districare il codice C ++ di qualcun altro. E sappiamo tutti che il codice C ++ di qualcun altro è un disastro completo quasi per definizione :) Quindi la prima cosa che faccio per decifrare il flusso di dati locali è mettere const in ogni definizione di variabile fino a quando il compilatore inizia ad abbaiare. Questo significa anche argomenti con valore di qualifica const, perché sono solo variabili locali elaborate inizializzate dal chiamante.

Ah, vorrei che le variabili fossero const per impostazione predefinita e che fosse necessario mutable per le variabili non const :)


4
"Vorrei che le variabili fossero const di default" - un ossimoro ?? Cool Seriamente, in che modo "consting" ti aiuta a districare il codice? Se lo scrittore originale ha cambiato un argomento apparentemente costante, come fai a sapere che il var doveva essere una costante? Inoltre, la stragrande maggioranza delle variabili (senza argomento) sono pensate per essere ... variabili. Quindi il compilatore dovrebbe rompersi molto presto dopo aver avviato il processo, no?

8
@ysap, 1. Contrassegnare const il più possibile mi permette di vedere quali parti si stanno muovendo e quali no. Nella mia esperienza, molti locali sono di fatto costanti, non viceversa. 2. "Variabile costante" / "Variabile immutabile" può sembrare ossimoro, ma è una pratica standard nei linguaggi funzionali, così come in alcuni non funzionali; vedi Rust per esempio: doc.rust-lang.org/book/variable-bindings.html
Constantin

1
Anche standard ora in alcune circostanze in c ++; per esempio, la lambda [x](){return ++x;}è un errore; vedi qui
anatolyg

10
Le variabili sono " const" di default in Rust :)
phoenix

Le variabili non devono necessariamente essere assegnabili per consentire loro di variare; il valore con cui sono inizializzati può anche variare in fase di esecuzione.
Sphynx,

80

Le seguenti due righe sono funzionalmente equivalenti:

int foo (int a);
int foo (const int a);

Ovviamente non sarai in grado di modificare anel corpo foose è definito nel secondo modo, ma non c'è differenza dall'esterno.

Dove è constveramente utile è con i parametri di riferimento o puntatore:

int foo (const BigStruct &a);
int foo (const BigStruct *a);

Ciò significa che foo può accettare un parametro di grandi dimensioni, forse una struttura dati di dimensioni gigabyte, senza copiarlo. Inoltre, dice al chiamante "Foo non cambierà * il contenuto di quel parametro". Il passaggio di un riferimento const consente inoltre al compilatore di prendere determinate decisioni sulle prestazioni.

*: A meno che non elimini la costanza, ma questo è un altro post.


3
Non si tratta di questa domanda; ovviamente per argomenti referenziati o puntati è una buona idea usare const (se il valore referenziato o puntato non viene modificato). Si noti che non è il parametro const nell'esempio del puntatore; è la cosa che indica il parametro.
tml,

> Il passaggio di un riferimento const consente inoltre al compilatore di prendere determinate decisioni sulle prestazioni. fallacia classica - il compilatore deve determinare da sé la costanza, la parola chiave const non aiuta a farlo grazie a aliasing puntatore e const_cast
jheriko

73

Le costanti superflue extra sono cattive dal punto di vista API:

Inserendo costanti superflue extra nel codice per i parametri di tipo intrinseci passati dal valore, la tua API si confonde senza fare promesse significative al chiamante o all'utente API (ostacola solo l'implementazione).

Troppe 'const' in un'API quando non necessarie sono come " piangere lupo ", alla fine le persone inizieranno a ignorare 'const' perché è ovunque e non significa nulla per la maggior parte del tempo.

L'argomento "reductio ad absurdum" per i costi extra in API è buono per questi primi due punti sarebbe se più parametri const fossero buoni, quindi ogni argomento che può avere una const su di esso, DOVREBBE avere una const su di esso. In effetti, se fosse davvero così buono, vorresti che const fosse il valore predefinito per i parametri e che una parola chiave come "mutabile" fosse solo quando vuoi cambiare il parametro.

Quindi proviamo a inserire const ovunque possiamo:

void mungerum(char * buffer, const char * mask, int count);

void mungerum(char * const buffer, const char * const mask, const int count);

Considera la riga di codice sopra. Non solo la dichiarazione è più ingombra, più lunga e più difficile da leggere, ma tre delle quattro parole chiave "const" possono essere tranquillamente ignorate dall'utente API. Tuttavia, l'uso extra di 'const' ha reso la seconda riga potenzialmente PERICOLOSA!

Perché?

Una rapida lettura errata del primo parametro char * const bufferpotrebbe farti pensare che non modificherà la memoria nel buffer di dati che viene passato - tuttavia, questo non è vero! Una "const" superflua può portare a presupposti pericolosi e non corretti sull'API se sottoposti a scansione o letti erroneamente rapidamente.


Le const superflue sono cattive anche dal punto di vista dell'implementazione del codice:

#if FLEXIBLE_IMPLEMENTATION
       #define SUPERFLUOUS_CONST
#else
       #define SUPERFLUOUS_CONST             const
#endif

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count);

Se FLEXIBLE_IMPLEMENTATION non è vero, l'API promette di non implementare la funzione nel primo modo seguente.

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       // Will break if !FLEXIBLE_IMPLEMENTATION
       while(count--)
       {
              *dest++=*source++;
       }
}

void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source, SUPERFLUOUS_CONST int count)
{
       for(int i=0;i<count;i++)
       {
              dest[i]=source[i];
       }
}

È una promessa molto sciocca da fare. Perché dovresti fare una promessa che non dà alcun vantaggio al tuo chiamante e limita solo la tua implementazione?

Entrambe sono implementazioni perfettamente valide della stessa funzione, quindi tutto ciò che hai fatto è legato inutilmente una mano dietro la schiena.

Inoltre, è una promessa molto superficiale che è facilmente (e legalmente elusa).

inline void bytecopyWrapped(char * dest,
   const char *source, int count)
{
       while(count--)
       {
              *dest++=*source++;
       }
}
void bytecopy(char * SUPERFLUOUS_CONST dest,
   const char *source,SUPERFLUOUS_CONST int count)
{
    bytecopyWrapped(dest, source, count);
}

Senti, l'ho implementato in quel modo, anche se avevo promesso di non farlo, semplicemente usando una funzione wrapper. È come quando il cattivo promette di non uccidere qualcuno in un film e ordina invece al suo scagnozzo di ucciderli.

Quelle costose superflue non valgono niente più che una promessa di un cattivo film.


Ma la capacità di mentire peggiora ancora:

Mi è stato chiarito che è possibile non corrispondere a const in header (dichiarazione) e codice (definizione) usando const spurio. I sostenitori della const-happy affermano che questa è una buona cosa poiché ti consente di inserire const solo nella definizione.

// Example of const only in definition, not declaration
class foo { void test(int *pi); };
void foo::test(int * const pi) { }

Tuttavia, il contrario è vero ... puoi inserire una const spuria solo nella dichiarazione e ignorarla nella definizione. Questo rende solo una const superflua in un'API più una cosa terribile e una bugia orribile - vedi questo esempio:

class foo
{
    void test(int * const pi);
};

void foo::test(int *pi) // Look, the const in the definition is so superfluous I can ignore it here
{
    pi++;  // I promised in my definition I wouldn't modify this
}

Tutto ciò che la const superflua effettivamente fa è rendere il codice dell'implementatore meno leggibile costringendolo a usare un'altra copia locale o una funzione wrapper quando vuole cambiare la variabile o passare la variabile per riferimento non const.

Guarda questo esempio. Qual è più leggibile? È ovvio che l'unica ragione per la variabile extra nella seconda funzione è perché alcuni designer di API hanno lanciato una const superflua?

struct llist
{
    llist * next;
};

void walkllist(llist *plist)
{
    llist *pnext;
    while(plist)
    {
        pnext=plist->next;
        walk(plist);
        plist=pnext;    // This line wouldn't compile if plist was const
    }
}

void walkllist(llist * SUPERFLUOUS_CONST plist)
{
    llist * pnotconst=plist;
    llist *pnext;
    while(pnotconst)
    {
        pnext=pnotconst->next;
        walk(pnotconst);
        pnotconst=pnext;
    }
}

Speriamo di aver imparato qualcosa qui. La const superflua è un pugno nell'occhio che ingombra l'API, un fastidioso fastidio, una promessa superficiale e insignificante, un ostacolo inutile e occasionalmente porta a errori molto pericolosi.


9
Perché i downvotes? È molto più utile se lasci un breve commento su un downvote.
Adisak,

7
Il punto centrale dell'uso dell'argomento const è di far fallire la linea contrassegnata (plist = pnext). È una misura di sicurezza ragionevole mantenere immutabile l'argomento della funzione. Concordo con il tuo punto sul fatto che sono cattivi nelle dichiarazioni di funzioni (poiché sono superflous), ma possono servire ai loro scopi nel blocco di implementazione.
touko

23
@Adisak Non vedo nulla di sbagliato nella tua risposta, di per sé, ma dai tuoi commenti sembra che ti manchi un punto importante. La definizione / implementazione della funzione non fa parte dell'API, che è solo la dichiarazione della funzione . Come hai detto, dichiarare le funzioni con parametri const è inutile e aggiunge disordine. Tuttavia, gli utenti dell'API potrebbero non aver mai bisogno di vederne l'implementazione. Nel frattempo, l'implementatore può decidere di qualificare alcuni parametri nella definizione della funzione SOLO per chiarezza, il che va benissimo.
jw013,

17
@ jw013 è corretto void foo(int)e void foo(const int)hanno la stessa funzione, non sovraccarichi. ideone.com/npN4W4 ideone.com/tZav9R La const qui è solo un dettaglio di implementazione del corpo della funzione e non ha alcun effetto sulla risoluzione del sovraccarico. Lascia const dalla dichiarazione, per un'API più sicura e più ordinata, ma inserisci const nella definizione , se intendi non modificare il valore copiato.
Oktalist,

3
@Adisak So che questo è vecchio, ma credo che l'uso corretto per un'API pubblica sarebbe il contrario. In questo modo gli sviluppatori che lavorano sugli interni non commettono errori come pi++quando non dovrebbero.
Caffè

39

const avrebbe dovuto essere l'impostazione predefinita in C ++. Come questo :

int i = 5 ; // i is a constant

var int i = 5 ; // i is a real variable

8
Esattamente i miei pensieri.
Constantin,

24
La compatibilità con C è troppo importante, almeno per le persone che progettano C ++, anche solo per considerarlo.

4
Interessante, non ci avevo mai pensato.
Dan,

6
Allo stesso modo, unsignedavrebbe dovuto essere l'impostazione predefinita in C ++. In questo modo: int i = 5; // i is unsignede signed int i = 5; // i is signed.
hkBattousai

25

Quando ho programmato C ++ per vivere, ho constatato tutto ciò che potevo. L'uso di const è un ottimo modo per aiutare il compilatore ad aiutarti. Ad esempio, la costituzione dei valori di ritorno del metodo può salvarti da errori di battitura come:

foo() = 42

quando intendevi:

foo() == 42

Se foo () è definito per restituire un riferimento non const:

int& foo() { /* ... */ }

Il compilatore ti consentirà felicemente di assegnare un valore al temporaneo anonimo restituito dalla chiamata di funzione. Rendendolo const:

const int& foo() { /* ... */ }

Elimina questa possibilità.


6
Con quale compilatore ha funzionato? GCC genera un errore durante il tentativo di compilazione foo() = 42: errore: lvalue richiesto come operando sinistro dell'assegnazione
gavrie

Questo è semplicemente errato. foo () = 42 è uguale a 2 = 3, ovvero un errore del compilatore. E restituire una const è completamente inutile. Non fa nulla per un tipo incorporato.
Josh,

2
Ho riscontrato questo utilizzo di const e posso dirti che alla fine produce molto più fastidio che benefici. Suggerimento: const int foo()è di tipo diverso da int foo(), il che ti mette in grossi problemi se stai usando cose come puntatori a funzione, sistemi di segnale / slot o boost :: bind.
Mephane,

2
Ho corretto il codice per includere il valore di ritorno di riferimento.
Avdi,

Non è const int& foo()effettivamente lo stesso int foo(), a causa dell'ottimizzazione del valore di ritorno?
Zantier,

15

C'è una buona discussione su questo argomento nei vecchi articoli del "Guru della settimana" su comp.lang.c ++. Moderato Qui .

L'articolo GOTW corrispondente è disponibile sul sito Web di Herb Sutter qui .


1
Herb Sutter è un ragazzo davvero intelligente :-) Sicuramente vale la pena leggere e concordo con TUTTI i suoi punti.
Adisak,

2
Bene un buon articolo ma non sono d'accordo con lui riguardo agli argomenti. Le faccio anche costanti perché sono come variabili e non voglio mai che nessuno apporti modifiche ai miei argomenti.
QBziZ,

9

Dico const i tuoi parametri di valore.

Considera questa funzione buggy:

bool isZero(int number)
{
  if (number = 0)  // whoops, should be number == 0
    return true;
  else
    return false;
}

Se il parametro number fosse const, il compilatore si fermerebbe e ci avviserebbe del bug.


2
un altro modo è usare if (0 == numero) ... else ...;
Johannes Schaub - litb

5
@ ChrisHuang-Leaver Orribile non è, se parla come Yoda si fa: stackoverflow.com/a/2430307/210916
MPelletier

GCC / Clang -Wall ti dà -Wparentheses, che ti richiede di farlo "if ((number = 0))" se questo è davvero quello che intendevi fare. Che funziona bene come sostituto di essere Yoda.
Jetski tipo S

8

Uso i parametri della funzione const on che sono riferimenti (o puntatori) che sono solo [in] dati e non verranno modificati dalla funzione. Significato, quando lo scopo di utilizzare un riferimento è evitare la copia dei dati e non consentire la modifica del parametro passato.

Mettere const sul parametro b booleano nel tuo esempio mette solo un vincolo sull'implementazione e non contribuisce all'interfaccia della classe (sebbene di solito non si consiglia di modificare i parametri).

La firma della funzione per

void foo(int a);

e

void foo(const int a);

è lo stesso, il che spiega il tuo .c e .h

Asaf


6

Se si utilizza il ->*o.* operatori , è un must.

Ti impedisce di scrivere qualcosa del genere

void foo(Bar *p) { if (++p->*member > 0) { ... } }

cosa che ho quasi fatto in questo momento, e che probabilmente non fa ciò che intendi.

Quello che intendevo dire era

void foo(Bar *p) { if (++(p->*member) > 0) { ... } }

e se avessi messo una via constdi mezzo Bar *e p, il compilatore me lo avrebbe detto.


4
Verificherei immediatamente un riferimento sulla precedenza degli operatori quando sto per mescolare insieme molti operatori (se non ne conosco già il 100%), quindi l'IMO non è un problema.
MK12,

5

Ah, difficile. Da un lato, una dichiarazione è un contratto e non ha davvero senso passare un argomento const in base al valore. D'altra parte, se si guarda all'implementazione della funzione, si danno al compilatore maggiori possibilità di ottimizzare se si dichiara una costante di argomento.


5

const è inutile quando l'argomento viene passato per valore poiché non modificherai l'oggetto del chiamante.

const dovrebbe essere preferito quando si passa per riferimento, a meno che lo scopo della funzione non sia modificare il valore passato.

Infine, una funzione che non modifica l'oggetto corrente (questo) può, e probabilmente dovrebbe essere dichiarata const. Un esempio è di seguito:

int SomeClass::GetValue() const {return m_internalValue;}

Questa è una promessa di non modificare l'oggetto a cui viene applicata questa chiamata. In altre parole, puoi chiamare:

const SomeClass* pSomeClass;
pSomeClass->GetValue();

Se la funzione non fosse const, ciò comporterebbe un avviso del compilatore.


5

Contrassegnare i parametri di valore "const" è sicuramente una cosa soggettiva.

Tuttavia in realtà preferisco contrassegnare i parametri di valore const, proprio come nel tuo esempio.

void func(const int n, const long l) { /* ... */ }

Il valore per me indica chiaramente che i valori dei parametri della funzione non vengono mai modificati dalla funzione. Avranno lo stesso valore all'inizio che alla fine. Per me, fa parte del mantenere uno stile di programmazione molto funzionale.

Per una funzione breve, è probabilmente uno spreco di tempo / spazio avere la 'const' lì, dal momento che di solito è abbastanza ovvio che gli argomenti non sono modificati dalla funzione.

Tuttavia, per una funzione più ampia, è una forma di documentazione di implementazione ed è applicata dal compilatore.

Posso essere sicuro che se eseguo un calcolo con 'n' e 'l', posso refactoring / spostare quel calcolo senza paura di ottenere un risultato diverso perché ho perso un posto in cui uno o entrambi sono cambiati.

Poiché si tratta di un dettaglio dell'implementazione, non è necessario dichiarare i parametri del valore const nell'intestazione, proprio come non è necessario dichiarare i parametri della funzione con gli stessi nomi utilizzati dall'implementazione.


4

Potrebbe essere questo non sarà un argomento valido. ma se incrementiamo il valore di una variabile const all'interno di un compilatore di funzioni ci darà un errore: " errore: incremento del parametro di sola lettura ". quindi ciò significa che possiamo usare la parola chiave const come un modo per impedire la modifica accidentale delle nostre variabili all'interno delle funzioni (cosa che non dovremmo / sola lettura). quindi se lo facessimo accidentalmente durante la compilazione, il compilatore ce lo farebbe sapere. questo è particolarmente importante se non sei l'unico che sta lavorando a questo progetto.


3

Tendo a usare const dove possibile. (O altra parola chiave appropriata per la lingua di destinazione.) Lo faccio puramente perché consente al compilatore di apportare ulteriori ottimizzazioni che altrimenti non sarebbe in grado di fare. Dal momento che non ho idea di quali possano essere queste ottimizzazioni, lo faccio sempre, anche dove sembra sciocco.

Per quanto ne so, il compilatore potrebbe benissimo vedere un parametro di valore const e dire: "Ehi, questa funzione non lo sta modificando comunque, quindi posso passare per riferimento e salvare alcuni cicli di clock". Non penso che farebbe mai una cosa del genere, dal momento che cambia la firma della funzione, ma sottolinea il punto. Forse fa una manipolazione dello stack diversa o qualcosa del genere ... Il punto è che non lo so, ma so che cercare di essere più intelligente di quanto il compilatore mi faccia solo vergognare.

Il C ++ ha qualche bagaglio extra, con l'idea di correttezza const, quindi diventa ancora più importante.


Sebbene possa aiutare in alcuni casi, sospetto che la possibilità di promuovere ottimizzazioni sia drammaticamente sopravvalutata a vantaggio di const. Piuttosto, si tratta di dichiarare l'intento all'interno dell'implementazione e catturare i pensieri in un secondo momento (incrementando accidentalmente la variabile locale errata, perché non lo era const). Parallelamente, aggiungerei anche che i compilatori sono i benvenuti a cambiare le firme delle funzioni, nel senso che le funzioni possono essere incorporate e, una volta sottolineate, è possibile modificare l'intero modo in cui funzionano; l'aggiunta o la rimozione di riferimenti, la creazione di valori letterali "variabili", ecc. sono tutti all'interno della regola as-if
underscore_d

3

1. Migliore risposta basata sulla mia valutazione:

La risposta di @Adisak è la migliore risposta qui in base alla mia valutazione. Si noti che questa risposta è in parte la migliore perché è anche la più supportata da esempi di codice reali , oltre a usare una logica sonora e ben ponderata.

2. Le mie parole (concordando con la migliore risposta):

  1. Per valore pass-by non vi è alcun vantaggio nell'aggiungere const. Tutto ciò che fa è:
    1. limitare l'implementatore a dover fare una copia ogni volta che desidera modificare un parametro di input nel codice sorgente (la cui modifica non avrebbe comunque effetti collaterali poiché ciò che viene passato è già una copia poiché è un valore pass-by). E spesso, la modifica di un parametro di input che viene passato per valore viene utilizzata per implementare la funzione, quindi l'aggiunta constovunque può ostacolarla.
    2. e aggiungendo constinutilmente ingombri il codice con consts ovunque, distogliendo l'attenzione dagli consts che sono veramente necessari per avere un codice sicuro.
  2. Quando si tratta di puntatori o riferimenti , tuttavia, constè di fondamentale importanza quando necessario e deve essere utilizzato, in quanto impedisce effetti collaterali indesiderati con modifiche persistenti al di fuori della funzione e pertanto ogni singolo puntatore o riferimento deve utilizzare constquando il parametro è solo un input, non un'uscita. L'utilizzo const solo sui parametri passati per riferimento o puntatore ha l'ulteriore vantaggio di rendere davvero ovvio quali parametri sono puntatori o riferimenti. È ancora una cosa da sporgere e dire "Attento! Qualsiasi parametro con constaccanto è un riferimento o un puntatore!".
  3. Quello che ho descritto sopra è stato spesso il consenso raggiunto nelle organizzazioni di software professionali in cui ho lavorato ed è stato considerato la migliore pratica. A volte anche, la regola è stata rigorosa: "non usare mai parametri cost che vengono passati per valore, ma usarli sempre su parametri passati per riferimento o puntatore se sono solo input".

3. Parole di Google (d'accordo con me e la migliore risposta):

(Dal " Guida allo stile di Google C ++ ")

Per un parametro di funzione passato per valore, const non ha alcun effetto sul chiamante, quindi non è raccomandato nelle dichiarazioni di funzione. Vedi TotW # 109 .

L'uso di const su variabili locali non è né incoraggiato né scoraggiato.

Fonte: la sezione "Uso di const" della Guida allo stile di Google C ++: https://google.github.io/styleguide/cppguide.html#Use_of_const . Questa è in realtà una sezione davvero preziosa, quindi leggi l'intera sezione.

Si noti che "TotW # 109" sta per "Suggerimento della settimana # 109: significativo constnelle dichiarazioni di funzioni" , ed è anche una lettura utile. È più informativo e meno prescrittivo su cosa fare, e in base al contesto è venuto prima della regola della Guida allo stile di Google C ++ sopra constcitata sopra, ma a causa della chiarezza fornita, la constregola citata sopra è stata aggiunta a Google C ++ Guida di stile.

Inoltre, anche se sto citando la Guida allo stile di Google C ++ qui a difesa della mia posizione, NON significa che seguo sempre la guida o che consiglio sempre di seguire la guida. Alcune delle cose che raccomandano sono semplicemente strane, come la loro kDaysInAWeekconvenzione di denominazione in stile per "Constant Names" . Tuttavia, è comunque utile e pertinente sottolineare quando una delle società tecniche e software di maggior successo e influenti del mondo utilizza la stessa giustificazione che io e altri come @Adisak facciamo per sostenere i nostri punti di vista su questo argomento.

4. Linter di Clang, clang-tidy , ha alcune opzioni per questo:

A. È anche interessante notare che linter di Clang, clang-tidyha un'opzione readability-avoid-const-params-in-decls, qui descritta , per sostenere applicare in una base di codice non utilizzare constper i parametri di funzione pass-by-value :

Verifica se una dichiarazione di funzione ha parametri che sono cost di livello superiore.

i valori const nelle dichiarazioni non influiscono sulla firma di una funzione, quindi non devono essere inseriti lì.

Esempi:

void f(const string);   // Bad: const is top level.
void f(const string&);  // Good: const is not top level.

E qui ci sono altri due esempi che mi sto aggiungendo per completezza e chiarezza:

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

B. Ha anche questa opzione: readability-const-return-type- https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

5. Il mio approccio pragmatico a come definirei una guida di stile sull'argomento:

Vorrei semplicemente copiarlo e incollarlo nella mia guida di stile:

[COPIA / INIZIO INCOLLA]

  1. Utilizzare sempre i const parametri della funzione passati per riferimento o puntatore quando il loro contenuto (ciò a cui indicano) NON intende essere modificato. In questo modo, diventa ovvio quando si prevede che una variabile passata per riferimento o puntatore venga modificata, perché mancherà const. In questo caso d'usoconst previene effetti collaterali accidentali al di fuori della funzione.
  2. Non è consigliabile utilizzare i constparametri di funzione passati per valore, perchéconst non ha alcun effetto sul chiamante: anche se la variabile viene modificata nella funzione non ci saranno effetti collaterali al di fuori della funzione. Consulta le seguenti risorse per ulteriori giustificazioni e approfondimenti:
    1. "Guida allo stile di Google C ++" Sezione "Uso di const"
    2. "Suggerimento della settimana n. 109: constdichiarazioni significative nelle funzioni"
    3. Stack Overflow di Adisak risponde su "Uso di 'const' per i parametri delle funzioni"
  3. " Non utilizzare mai il livello superiore const[ovvero: constsui parametri passati per valore ] sui parametri delle funzioni in dichiarazioni che non sono definizioni (e fare attenzione a non copiare / incollare un significato insignificante const). È insignificante e ignorato dal compilatore, è rumore visivo e potrebbe fuorviare i lettori "( https://abseil.io/tips/109 , enfasi aggiunta).
    1. Gli unici constqualificatori che hanno un effetto sulla compilazione sono quelli inseriti nella definizione della funzione, NON quelli in una dichiarazione diretta della funzione, come in una dichiarazione di funzione (metodo) in un file di intestazione.
  4. Non utilizzare mai il livello superiore const[ovvero: constsulle variabili passate per valore ] sui valori restituiti da una funzione.
  5. L'uso di constpuntatori o riferimenti restituiti da una funzione dipende dall'implementatore , poiché a volte è utile.
  6. TODO: imporre alcune delle precedenti con quanto segue clang-tidy opzioni opzioni:
    1. https://clang.llvm.org/extra/clang-tidy/checks/readability-avoid-const-params-in-decls.html
    2. https://clang.llvm.org/extra/clang-tidy/checks/readability-const-return-type.html

Ecco alcuni esempi di codice per dimostrare le constregole sopra descritte:

constEsempi di parametri:
(alcuni sono presi in prestito da qui )

void f(const std::string);   // Bad: const is top level.
void f(const std::string&);  // Good: const is not top level.

void f(char * const c_string);   // Bad: const is top level. [This makes the _pointer itself_, NOT what it points to, const]
void f(const char * c_string);   // Good: const is not top level. [This makes what is being _pointed to_ const]

constEsempi di tipo di ritorno:
(alcuni sono presi in prestito da qui )

// BAD--do not do this:
const int foo();
const Clazz foo();
Clazz *const foo();

// OK--up to the implementer:
const int* foo();
const int& foo();
const Clazz* foo();

[COPIA / FINE PASTA]


2

Nel caso in cui menzioni, non influisce sui chiamanti della tua API, motivo per cui non è comunemente fatto (e non è necessario nell'intestazione). Influisce solo sull'implementazione della tua funzione.

Non è una cosa particolarmente brutta da fare, ma i vantaggi non sono così grandi dato che non influisce sull'API e aggiunge la digitazione, quindi di solito non è fatto.


2

Non utilizzo const per parametere con valore superato. Al chiamante non importa se si modifica o meno il parametro, si tratta di un dettaglio di implementazione.

Ciò che è veramente importante è contrassegnare i metodi come const se non modificano la loro istanza. Fallo mentre procedi, perché altrimenti potresti finire con un sacco di const_cast <> o potresti scoprire che la marcatura di un metodo const richiede la modifica di molto codice perché chiama altri metodi che avrebbero dovuto essere contrassegnati const.

Tendo anche a contrassegnare le variabili locali se non ho bisogno di modificarle. Credo che renda più facile la comprensione del codice facilitando l'identificazione delle "parti mobili".



2

Uso const dove posso. Costare per i parametri significa che non dovrebbero cambiare il loro valore. Ciò è particolarmente utile quando si passa per riferimento. const per la funzione dichiara che la funzione non deve modificare i membri delle classi.


2

Riassumere:

  • "Normalmente const per valore è inutile e fuorviante nella migliore delle ipotesi." Da GOTW006
  • Ma puoi aggiungerli in .cpp come faresti con le variabili.
  • Si noti che la libreria standard non utilizza const. Es std::vector::at(size_type pos). Ciò che è abbastanza buono per la libreria standard è buono per me.

2
"Ciò che è abbastanza buono per la libreria standard è buono per me" non è sempre vero. Ad esempio, la libreria standard utilizza brutti nomi di variabili come _Tmpsempre - non vuoi questo (in realtà non ti è permesso usarli).
Anatolyg,

1
@anatolyg questo è un dettaglio di implementazione
Fernando Pelliccioni,

2
OK, sia i nomi delle variabili che i tipi qualificati nelle liste di argomenti sono dettagli di implementazione. Quello che voglio dire è che l'implementazione della libreria standard a volte non è buona. A volte, puoi (e dovresti) fare di meglio. Quando è stato scritto il codice per la libreria standard - 10 anni fa? 5 anni fa (alcune parti più recenti)? Oggi possiamo scrivere codice migliore.
Anatolyg,

1

Se il parametro viene passato per valore (e non è un riferimento), di solito non c'è molta differenza se il parametro viene dichiarato come const o meno (a meno che non contenga un membro di riferimento - non è un problema per i tipi predefiniti). Se il parametro è un riferimento o un puntatore, di solito è meglio proteggere la memoria di riferimento / puntata, non il puntatore stesso (penso che non sia possibile rendere costante il riferimento stesso, non che sia importante poiché non è possibile modificare l'arbitro) . Sembra una buona idea proteggere tutto ciò che puoi come const. Puoi ometterlo senza paura di fare un errore se i parametri sono solo POD (compresi i tipi incorporati) e non c'è alcuna possibilità che cambino ulteriormente lungo la strada (ad esempio nel tuo esempio il parametro bool).

Non sapevo della differenza di dichiarazione del file .h / .cpp, ma ha un senso. A livello di codice macchina, nulla è "const", quindi se si dichiara una funzione (in .h) come non const, il codice è lo stesso che se lo si dichiara const (ottimizzazioni a parte). Tuttavia, ti aiuta a elencare il compilatore che non modificherai il valore della variabile all'interno dell'implementazione della funzione (.ccp). Potrebbe essere utile nel caso in cui si erediti da un'interfaccia che consenta la modifica, ma non è necessario modificare i parametri per ottenere la funzionalità richiesta.


0

Non metterei costanti parametri del genere: tutti sanno già che un booleano (al contrario di un booleano &) è costante, quindi aggiungerlo farà pensare alle persone "aspetta, cosa?" o anche che stai passando il parametro per riferimento.


4
a volte vorresti passare un oggetto per riferimento (per motivi di prestazioni) ma non modificarlo, quindi const è obbligatorio allora. Mantenere tutti questi parametri - anche i bool - const sarebbe quindi una buona pratica, rendendo più facile la lettura del codice.
gbjbaanb,

0

la cosa da ricordare con const è che è molto più facile rendere le cose dall'inizio, piuttosto che cercare di inserirle in un secondo momento.

Usa const quando vuoi che qualcosa rimanga invariato: è un suggerimento aggiuntivo che descrive cosa fa la tua funzione e cosa aspettarti. Ho visto molte API C che potrebbero avere a che fare con alcune di esse, in particolare quelle che accettano stringhe a C!

Sarei più propenso a omettere la parola chiave const nel file cpp rispetto all'intestazione, ma poiché tendo a tagliarli + incollarli, verrebbero mantenuti in entrambi i punti. Non ho idea del perché il compilatore lo consenta, immagino sia una cosa da compilatore. La migliore pratica è sicuramente quella di inserire la parola chiave const in entrambi i file.


Non capisco affatto. Perché dovresti essere incline a ometterlo nel file cpp (definizione della funzione)? Ecco dove in realtà significa qualcosa e può rilevare errori. Perché pensi che sia la migliore pratica mettere const in entrambi i posti? Nel file di intestazione (dichiarazione di funzione), non significa nulla e ingombra l'API. Forse c'è un piccolo valore nell'avere il decl e il defn esattamente identici, ma mi sembra che sia un vantaggio molto minore rispetto al problema di ingombrare l'API.
Don Hatch,

@DonHatch 8 anni dopo, wow. Ad ogni modo, come ha detto l'OP "Sono stato anche sorpreso di apprendere che è possibile omettere const da parametri in una dichiarazione di funzione, ma è possibile includerlo nella definizione di funzione".
gbjbaanb,

0

Non c'è davvero alcun motivo per creare un valore-parametro "const" poiché la funzione può comunque modificare solo una copia della variabile.

Il motivo per usare "const" è se stai passando qualcosa di più grande (ad esempio una struttura con molti membri) per riferimento, nel qual caso garantisce che la funzione non possa modificarlo; o meglio, il compilatore si lamenterà se si tenta di modificarlo in modo convenzionale. Impedisce che venga modificato accidentalmente.


0

Il parametro Const è utile solo quando il parametro viene passato per riferimento, ovvero riferimento o puntatore. Quando il compilatore vede un parametro const, si assicura che la variabile utilizzata nel parametro non venga modificata all'interno del corpo della funzione. Perché qualcuno dovrebbe voler rendere costante un parametro per valore? :-)


Per molte ragioni. Stabilire un parametro per valore constindica chiaramente: 'Non ho bisogno di modificarlo, quindi lo sto dichiarando. Se provo a modificarlo in un secondo momento, dammi un errore di compilazione in modo da poter correggere il mio errore o deselezionare come const". Quindi è una questione di igiene e sicurezza del codice. Per tutto ciò che serve per aggiungere ai file di implementazione, dovrebbe essere qualcosa che le persone fanno come puro riflesso, IMO.
underscore_d

0

Dato che i parametri vengono passati per valore, non fa alcuna differenza se si specifica const o no dal punto di vista della funzione chiamante.In pratica non ha senso dichiarare passare i parametri di valore come const.


0

Tutti i contro nei tuoi esempi non hanno scopo. Il C ++ è un valore pass-by per impostazione predefinita, quindi la funzione ottiene copie di questi interi e booleani. Anche se la funzione li modifica, la copia del chiamante non è interessata.

Quindi eviterei extra extra perché

  • Sono ridondanti
  • Raggruppano il testo
  • Mi impediscono di modificare il valore passato nei casi in cui potrebbe essere utile o efficiente.

-1

So che la domanda è "un po '" obsoleta ma, man mano che mi imbattei, anche qualcun altro potrebbe farlo anche in futuro ... ... dubito ancora che il poveretto verrà elencato qui per leggere il mio commento :)

Mi sembra che siamo ancora troppo limitati al modo di pensare in stile C. Nel paradigma OOP giochiamo con oggetti, non tipi. L'oggetto Const può essere concettualmente diverso da un oggetto non const, in particolare nel senso di const-logico (in contrasto con const-bit). Pertanto, anche se la correttezza const dei parametri di funzione è (forse) un'eccessiva attenzione in caso di POD, non lo è in caso di oggetti. Se una funzione funziona con un oggetto const, dovrebbe dirlo. Considera il seguente frammento di codice

#include <iostream>

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
class SharedBuffer {
private:

  int fakeData;

  int const & Get_(int i) const
  {

    std::cout << "Accessing buffer element" << std::endl;
    return fakeData;

  }

public:

  int & operator[](int i)
  {

    Unique();
    return const_cast<int &>(Get_(i));

  }

  int const & operator[](int i) const
  {

    return Get_(i);

  }

  void Unique()
  {

    std::cout << "Making buffer unique (expensive operation)" << std::endl;

  }

};

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NonConstF(SharedBuffer x)
{

  x[0] = 1;

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void ConstF(const SharedBuffer x)
{

  int q = x[0];

}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int main()
{

  SharedBuffer x;

  NonConstF(x);

  std::cout << std::endl;

  ConstF(x);

  return 0;

}

ps .: puoi sostenere che il riferimento (const) sarebbe più appropriato qui e ti dà lo stesso comportamento. Bene Sto solo dando un'immagine diversa da quella che potrei vedere altrove ...

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.