c++ - Uso di 'const' per i parametri di funzione


Quanto vai lontano con const ? const funzioni const quando necessario o vai tutto il maiale e lo usi ovunque? Ad esempio, immagina un semplice mutatore che prende 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 ne valga la pena?

Sono stato anche sorpreso di apprendere che puoi omettere const dai parametri in una dichiarazione di funzione, ma puoi includerlo 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? Sembra un po 'insolito per me.




Answers


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 è sempre la stessa. Probabilmente è un pessimo stile farlo molto però.

Personalmente tendo a non usare const tranne i parametri di riferimento e puntatore. Per gli oggetti copiati non ha molta importanza, sebbene possa essere più sicuro in quanto segnala l'intento all'interno della funzione. È davvero una sentenza. Tendo ad usare const_iterator anche quando faccio il loop su qualcosa e non intendo modificarlo, quindi credo a ciascuno di essi, purché rigorosamente mantenuta la correttezza costante per i tipi di riferimento.




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

Sbagliato.

Si tratta di auto-documentare il tuo codice e le tue ipotesi.

Se il tuo codice ha molte persone che lavorano su di esso e le tue funzioni non sono banali, dovresti contrassegnare "const" tutto ciò che puoi. Quando scrivi un codice di forza industriale, dovresti sempre presumere che i tuoi colleghi siano psicopatici che cercano di ottenere in qualsiasi modo (soprattutto perché spesso è te stesso in futuro).

Inoltre, come qualcuno ha detto prima, potrebbe aiutare il compilatore ad ottimizzare un po 'le cose (anche se è un campo lungo).




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 locale è mettere const in ogni definizione di variabile finché il compilatore inizia ad abbaiare. Ciò significa anche argomenti valore const-qualifying, perché sono solo variabili locali elaborate inizializzate dal chiamante.

Ah, vorrei che le variabili fossero const per default e mutabile fosse richiesto per variabili non const :)




Le seguenti due righe sono funzionalmente equivalenti:

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

Ovviamente non sarai in grado di modificare a nel corpo di foo se è definito il secondo modo, ma non c'è differenza dall'esterno.

Dove const davvero utile è con parametri di riferimento o puntatore:

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

Quello che dice è che foo può prendere un grande parametro, forse una struttura dati di dimensioni gigabyte, senza copiarlo. Inoltre, dice al chiamante, "Foo non * cambierà il contenuto di quel parametro." Anche il passaggio a un riferimento const consente al compilatore di prendere determinate decisioni sulle prestazioni.

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




I cost extra superflui sono negativi da un punto di vista API:

Mettendo nel codice super extra superflui const per i parametri di tipo intrinseco passati dal valore ammucchia la tua API senza fare alcuna promessa significativa al chiamante o all'API dell'utente (ostacola solo l'implementazione).

Troppi "const" in un'API quando non servono è come " lupo che piange ", alla fine la gente comincerà a ignorare "const" perché è dappertutto e non significa gran parte del tempo.

L'argomento "reductio ad absurdum" per le eccezioni di cons in API è buono per questi primi due punti sarebbe se più parametri const sono buoni, quindi ogni argomento che può avere un const su di esso, DOVREBBE avere un const su di esso. In effetti, se fosse davvero così buono, vorresti che const sia l'impostazione predefinita per i parametri e che abbia una parola chiave come "mutabile" solo quando vuoi cambiare il parametro.

Quindi proviamo a mettere in const dove 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ù disordinata e più lunga e 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 linea potenzialmente PERICOLOSA!

Perché?

Una rapida lettura errata del primo parametro char * const buffer potrebbe farti pensare che non modificherà la memoria nel buffer di dati che viene passato - tuttavia, questo non è vero! 'Const' superfluo può portare a presupposti pericolosi e non corretti sulla tua API durante la scansione o la lettura errata.

I cost superflui sono negativi anche da un punto di vista di 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, allora l'API è "promettente" di non implementare la funzione nella prima maniera sotto.

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];
       }
}

Questa è una promessa molto stupida da fare. Perché dovresti fare una promessa che non dà alcun vantaggio al tuo interlocutore e limita la tua implementazione?

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

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

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);
}

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

Quelli superflui non valgono più di una promessa da un cattivo ragazzo del cinema.

Ma la capacità di mentire è ancora peggiore:

Sono stato illuminato che è possibile mismatch const nell'intestazione (dichiarazione) e nel codice (definizione) utilizzando spurious const. I sostenitori della buona volontà affermano che questa è una buona cosa dal momento che ti permette di mettere 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 mettere un const spurio solo nella dichiarazione e ignorarlo nella definizione. Questo rende solo il const superfluo in una API più di 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 il costosuperficante effettivamente fa è rendere il codice dello strumento di implementazione 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. Quale è più leggibile? È ovvio che l'unica ragione della variabile extra nella seconda funzione sia dovuta al fatto che alcuni progettisti di API hanno inserito un const superfluo?

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. Il superfluido const è un pugno nell'occhio che fa l'API, un fastidioso nag, una promessa superficiale e priva di significato, un ostacolo non necessario e, occasionalmente, porta a errori molto pericolosi.




const dovrebbe essere il default in C ++. Come questo :

int i = 5 ; // i is a constant

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



Quando ho codificato il C ++ per vivere ho posto tutto ciò che potevo. Usare const è un ottimo modo per aiutare il compilatore ad aiutarti. Ad esempio, la costante dei valori di ritorno del metodo può farti risparmiare errori di battitura come:

foo() = 42

quando intendevi:

foo() == 42

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

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

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

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

Elimina questa possibilità.




C'è una buona discussione su questo argomento nei vecchi articoli "Guru della settimana" su comp.lang.c ++, qui moderati.

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




Uso i parametri const su function che sono riferimenti (o puntatori) che sono solo [in] dati e non saranno modificati dalla funzione. Significato, quando lo scopo dell'utilizzo di un riferimento è evitare di copiare i dati e non consentire di modificare il parametro passato.

L'inserimento di const nel parametro b booleano nell'esempio mette solo un vincolo sull'implementazione e non contribuisce all'interfaccia della classe (sebbene di solito non sia consigliabile 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




Dico const i tuoi parametri di valore.

Considera questa funzione bacata:

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 avviserà del bug.




Se usi gli operatori ->* o .* , È obbligatorio.

Ti impedisce di scrivere qualcosa di simile

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

che ho quasi fatto adesso, e che probabilmente non fa quello che intendi.

Quello che intendevo dire era

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

e se avessi messo un const tra Bar * e p , il compilatore me lo avrebbe detto.




Ah, un duro. Da un lato, una dichiarazione è un contratto e in realtà non ha senso passare un argomento const per valore. D'altra parte, se si guarda all'implementazione della funzione, si dà al compilatore più possibilità di ottimizzare se si dichiara una costante di argomento.




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

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

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

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

Questa è una promessa per 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 avvertimento del compilatore.




Marcare i parametri del 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 come alla fine. Per me, fa parte del mantenere uno stile di programmazione molto funzionale.

Per una funzione breve, è indubbiamente uno spreco di tempo / spazio avere il 'const' lì, dato che di solito è abbastanza ovvio che gli argomenti non vengono modificati dalla funzione.

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

Posso essere sicuro che se faccio 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 di implementazione, non è necessario dichiarare i parametri value const nell'intestazione, proprio come non è necessario dichiarare i parametri di funzione con gli stessi nomi utilizzati dall'implementazione.




Nel caso in cui menzioni, non ha alcun effetto 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 grandi perché non influisce sulla tua API, e aggiunge la digitazione, quindi di solito non è fatta.




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

Ciò che è veramente importante è contrassegnare i metodi come const se non modificano la loro istanza. Fai questo mentre vai, perché altrimenti potresti finire con un sacco di const_cast <> o potresti scoprire che marcare un metodo const richiede di cambiare molto codice perché chiama altri metodi che dovrebbero essere stati marcati const.

Tendo anche a contrassegnare vars const locali se non ho bisogno di modificarli. Credo che faciliti la comprensione del codice rendendo più facile l'identificazione delle "parti mobili".




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

Per quanto ne so, il compilatore potrebbe benissimo vedere un parametro const value, e dire, "Ehi, questa funzione non lo sta comunque modificando, 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 rende il punto. Forse fa qualche manipolazione dello stack o qualcosa del genere ... Il punto è che non lo so, ma so che cercare di essere più intelligente del compilatore mi fa solo vergognare.

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




Io uso const se posso. Const per i parametri significa che non dovrebbero cambiare il loro valore. Questo è particolarmente utile quando si passa per riferimento. const per function dichiara che la funzione non dovrebbe cambiare i membri delle classi.




Potrebbe essere questo non sarà un argomento valido. ma se incrementiamo il valore di una variabile const all'interno di una funzione il compilatore ci darà un errore: " errore: incremento del parametro di sola lettura ". questo significa che possiamo usare la parola chiave const come un modo per evitare di modificare accidentalmente le nostre variabili all'interno delle funzioni (che non dovremmo leggere / leggere). quindi se lo avessimo fatto accidentalmente al momento della compilazione, il compilatore ce lo farebbe sapere. questo è particolarmente importante se non sei l'unico a lavorare su questo progetto.




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 sia modificata all'interno del corpo della funzione. Perché qualcuno dovrebbe voler rendere costante un parametro di valore? :-)




Se il parametro viene passato per valore (e non è un riferimento), di solito non c'è molta differenza se il parametro è dichiarato come const o no (a meno che non contenga un membro di riferimento - non un problema per i tipi built-in). Se il parametro è un riferimento o un puntatore, di solito è meglio proteggere la memoria referenziata / puntata, non il puntatore stesso (penso che non sia possibile rendere il riferimento stesso const, non che importi molto in quanto non è possibile cambiare l'arbitro) . Sembra una buona idea proteggere tutto ciò che puoi come const. Puoi ometterlo senza paura di commettere un errore se i parametri sono solo POD (inclusi i tipi built-in) e non c'è possibilità che cambino ulteriormente lungo la strada (ad esempio nell'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 dichiari una funzione (nel file .h) come non-const, il codice è lo stesso come se lo dichiarassi come const (ottimizzazioni a parte). Tuttavia, ti aiuta ad arruolare il compilatore per non modificare il valore della variabile all'interno dell'implementazione della funzione (.ccp). Potrebbe rivelarsi utile nel caso in cui si erediti da un'interfaccia che consente il cambiamento, ma non è necessario modificare i parametri per ottenere la funzionalità richiesta.







Riassumere:

  • "Normalmente il valore di passaggio const è inutile e fuorviante al meglio." Da GOTW006
  • Ma puoi aggiungerli in .cpp come faresti con le variabili.
  • Si noti che la libreria standard non usa const. Ad esempio, std::vector::at(size_type pos) . Ciò che è abbastanza buono per la libreria standard è buono per me.



Non metterei costanti su parametri come questo - tutti sanno già che un booleano (al contrario di un booleano e) è costante, quindi aggiungerlo farà sì che la gente pensi "aspetta, cosa?" o anche che stai passando il parametro per riferimento.




la cosa da ricordare con const è che è molto più facile rendere le cose fin dall'inizio, piuttosto che provare a inserirle in seguito.

Usa const quando vuoi che qualcosa rimanga invariato: è un suggerimento aggiunto che descrive cosa fa la tua funzione e cosa ti aspetti. Ho visto molte API C che potrebbero fare con alcune di esse, specialmente quelle che accettano c-stringhe!

Sarei più propenso ad omettere la parola chiave const nel file cpp rispetto all'intestazione, ma dato che tendo a tagliarli + incollarli, dovrebbero essere conservati in entrambi i punti. Non ho idea del perché il compilatore lo consenta, immagino sia una cosa del compilatore. La migliore pratica è sicuramente quella di mettere la tua parola chiave const in entrambi i file.




Non c'è davvero alcun motivo per impostare un parametro di valore "const" in quanto la funzione può modificare comunque solo una copia della variabile.

La ragione per usare "const" è se passi qualcosa di più grande (per esempio una struct con molti membri) per riferimento, nel qual caso garantisce che la funzione non possa modificarlo; o meglio, il compilatore si lamenterà se proverai a modificarlo nel modo convenzionale. Si impedisce che venga accidentalmente modificato.




Come parametri vengono passati per valore, doesnt fa alcuna differenza se si specifica const o no dal perspective.It della funzione di chiamata in fondo non ha alcun senso di dichiarare passaggio dai parametri di valore come const.




Tutti i const nei tuoi esempi non hanno scopo. C ++ è passare per valore di default, quindi la funzione ottiene copie di tali interi e booleani. Anche se la funzione non modificarli, copia del chiamante non è interessato.

Così Eviterei const extra perché

  • Sono redudant
  • Essi ingombrare il testo
  • Mi hanno impediscono di cambiare il passato in termini di valore nei casi in cui potrebbe essere utile o efficiente.



So che la domanda è "un po '" datato, ma come ho mai incontrato che qualcun altro può anche farlo in futuro ... ... ancora Dubito che il poveretto elencherà qui per leggere il mio commento :)

Mi sembra che siamo ancora troppo confinati in modo C-stile di pensiero. Nel paradigma OOP giochiamo in giro con oggetti, non tipi. const oggetto può essere concettualmente differente da un oggetto non const, in particolare nel senso logico-const (in contrasto con bit-const). Così, anche se const correttezza della funzione params è (forse) un eccesso di prudenza in caso di POD non è quindi in caso di oggetti. Se una funzione lavora con un oggetto const si dovrebbe dire così. Si consideri 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 .: si può sostenere che (const) di riferimento sarebbe più appropriato qui e ti dà lo stesso comportamento. Bene, giusto. Basta dare un quadro diverso da quello che ho potuto vedere altrove ...




Essendo un programmatore VB.NET che ha bisogno di utilizzare un programma C ++ con 50 + funzioni a vista, e un file .h che utilizza sporadicamente il qualificatore const, è difficile sapere quando di accedere a una variabile utilizzando ByRef o ByVal.

Naturalmente il programma ti dice generando un errore di eccezione sulla linea dove hai fatto l'errore, ma allora avete bisogno di capire quale dei parametri 2-10 è sbagliato.

Così ora ho il compito di cattivo gusto di cercare di convincere uno sviluppatore che in realtà dovrebbe definire le loro variabili (nel file h) in modo tale da consentire un metodo automatico di creazione di tutte le definizioni di funzione VB.NET facilmente. Saranno poi con aria di sufficienza dire: "leggere il ... documentazione."

Ho scritto uno script awk che analizza un file .h, e crea tutti i comandi di funzione dichiarare, ma senza un indicatore di quanto a cui le variabili sono R / O vs R / W, lo fa solo metà del lavoro.

MODIFICARE:

Alla promozione di un altro utente sto aggiungendo quanto segue;

Ecco un esempio di (IMO) mal formata entrata .h;

typedef int (EE_STDCALL *Do_SomethingPtr)( int smfID, const char* cursor_name, const char* sql );

Il VB risultante dal mio script;

    Declare Function Do_Something Lib "SomeOther.DLL" (ByRef smfID As Integer, ByVal cursor_name As String, ByVal sql As String) As Integer

Si noti la "const" mancante al primo parametro. Senza di esso, un programma (o un altro sviluppatore) non ha idea del primo parametro deve essere passato "ByVal". Aggiungendo la "const" rende il .h file auto documentare in modo che gli sviluppatori che utilizzano altri linguaggi possono facilmente scrivere codice funzionante.