Quanti e quali sono gli usi di "const" in C ++?


129

Come programmatore C ++ alle prime armi ci sono alcuni costrutti che mi sembrano ancora molto oscuri, uno di questi è const. Puoi usarlo in così tanti posti e con così tanti effetti diversi che è quasi impossibile per un principiante uscire vivo. Qualche guru del C ++ spiegherà una volta per sempre i vari usi e se e / o perché non usarli?


cercando esattamente quella domanda: D
alamin,

Risposte:


100

Cercare di raccogliere alcuni usi:

Vincolare alcuni temporanei a riferimento a const, per allungarne la durata. Il riferimento può essere una base - e il suo distruttore non ha bisogno di essere virtuale - il giusto distruttore è ancora chiamato:

ScopeGuard const& guard = MakeGuard(&cleanUpFunction);

Spiegazione , usando il codice:

struct ScopeGuard { 
    ~ScopeGuard() { } // not virtual
};

template<typename T> struct Derived : ScopeGuard { 
    T t; 
    Derived(T t):t(t) { }
    ~Derived() {
        t(); // call function
    }
};

template<typename T> Derived<T> MakeGuard(T t) { return Derived<T>(t); }

Questo trucco è usato nella classe di utilità ScopeGuard di Alexandrescu. Una volta che il temporaneo esce dal campo di applicazione, il distruttore di Derivato viene chiamato correttamente. Nel codice sopra mancano alcuni piccoli dettagli, ma questo è il grosso problema.


Usa const per dire agli altri che i metodi non cambieranno lo stato logico di questo oggetto.

struct SmartPtr {
    int getCopies() const { return mCopiesMade; }
};

Usa const per le classi copy-on-write , per fare in modo che il compilatore ti aiuti a decidere quando e quando non devi copiarlo.

struct MyString {
    char * getData() { /* copy: caller might write */ return mData; }
    char const* getData() const { return mData; }
};

Spiegazione : È possibile che si desideri condividere dati quando si copia qualcosa purché i dati dell'oggetto originale e delle copie rimangano invariati. Una volta che uno degli oggetti cambia i dati, ora sono necessarie due versioni: una per l'originale e una per la copia. Cioè, si copia su una scrittura su entrambi gli oggetti, in modo che entrambi abbiano la propria versione.

Utilizzando il codice :

int main() {
    string const a = "1234";
    string const b = a;
    // outputs the same address for COW strings
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Lo snippet sopra riportato stampa lo stesso indirizzo sul mio GCC, poiché la libreria C ++ utilizzata implementa una copia su scrittura std::string. Entrambe le stringhe, anche se sono oggetti distinti, condividono la stessa memoria per i loro dati di stringa. Fare bnon const preferirà la versione non const di operator[]e GCC creerà una copia del buffer di memoria di supporto, perché potremmo cambiarlo e non deve influenzare i dati di a!

int main() {
    string const a = "1234";
    string b = a;
    // outputs different addresses!
    cout << (void*)&a[0] << ", " << (void*)&b[0];
}

Affinché il costruttore di copie faccia copie da oggetti e provini const :

struct MyClass {
    MyClass(MyClass const& that) { /* make copy of that */ }
};

Per creare costanti che banalmente non possono cambiare

double const PI = 3.1415;

Per il passaggio di oggetti arbitrari per riferimento anziché per valore - per impedire il passaggio di valore potenzialmente costoso o impossibile

void PrintIt(Object const& obj) {
    // ...
}

2
Puoi spiegare per favore il primo e il terzo utilizzo nei tuoi esempi?
Tunnuz,

"Per garantire alla chiamata che il parametro non può essere NULL" Non vedo come const abbia a che fare con questo esempio.
Logan Capaldo

oops, così fallisco. in qualche modo ho iniziato a scrivere di riferimenti. grazie mille per il lamento :) Ovviamente rimuoverò quella roba ora :)
Johannes Schaub - litb

3
Per favore, spiega il primo esempio. Non ha molto senso per me.
Chikuba

28

Ci sono davvero 2 usi principali di const in C ++.

Valori costanti

Se un valore ha la forma di una variabile, un membro o un parametro che non sarà (o non dovrebbe essere) modificato durante la sua vita, dovresti contrassegnarlo come const. Questo aiuta a prevenire mutazioni sull'oggetto. Ad esempio, nella seguente funzione non ho bisogno di cambiare l'istanza Student passata, quindi la segnalo const.

void PrintStudent(const Student& student) {
  cout << student.GetName();
}

Quanto al perché lo faresti. È molto più facile ragionare su un algoritmo se sai che i dati sottostanti non possono cambiare. "const" aiuta, ma non garantisce che ciò verrà raggiunto.

Ovviamente, la stampa dei dati per cout non richiede molto pensiero :)

Contrassegnare un metodo membro come const

Nell'esempio precedente ho contrassegnato Student come const. Ma come faceva C ++ a sapere che la chiamata del metodo GetName () su student non avrebbe mutato l'oggetto? La risposta è che il metodo è stato contrassegnato come const.

class Student {
  public:
    string GetName() const { ... }
};

Contrassegnare un metodo "const" fa 2 cose. Principalmente dice a C ++ che questo metodo non muterà il mio oggetto. La seconda cosa è che tutte le variabili membro verranno ora trattate come se fossero contrassegnate come const. Questo aiuta, ma non ti impedisce di modificare l'istanza della tua classe.

Questo è un esempio estremamente semplice, ma speriamo che possa aiutare a rispondere alle tue domande.


16

Abbi cura di capire la differenza tra queste 4 dichiarazioni:

Le seguenti 2 dichiarazioni sono identiche semanticamente. Puoi cambiare il punto in cui ccp1 e ccp2 indicano, ma non puoi cambiare la cosa a cui stanno puntando.

const char* ccp1;
char const* ccp2;

Successivamente, il puntatore è const, quindi per essere significativo deve essere inizializzato per puntare a qualcosa. Non puoi farlo puntare a qualcos'altro, tuttavia la cosa a cui punta può essere cambiata.

char* const cpc = &something_possibly_not_const;

Infine, uniamo le due cose - quindi l'oggetto puntato non può essere modificato e il puntatore non può puntare a nessun altro.

const char* const ccpc = &const_obj;

La regola della spirale in senso orario può aiutare a districare una dichiarazione http://c-faq.com/decl/spiral.anderson.html


In una rotonda, sì, lo fa! La regola della spirale in senso orario lo descrive meglio: inizia dal nome (kpPointer) e disegna una spirale in senso orario che esce attraverso il token e dì ogni token. Ovviamente, non c'è nulla a destra di kpPointer ma funziona ancora.
Steve Folly,

3

Come piccola nota, mentre leggo qui , è utile notare questo

const si applica a tutto ciò che è alla sua sinistra immediata (tranne se non c'è nulla nel qual caso si applica a qualunque sia il suo diritto immediato).

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.