Quando utilizzare riferimenti o puntatori

Comprendo la syntax e la semantica generale dei puntatori rispetto ai riferimenti, ma come dovrei decidere quando è più o meno appropriato utilizzare riferimenti o puntatori in un’API?

Naturalmente alcune situazioni richiedono l’una o l’altra (l’ operator++ bisogno di un argomento di riferimento), ma in generale sto trovando che preferisco usare puntatori (e puntatori const) poiché la syntax è chiara che le variabili vengono passate in modo distruttivo.

Ad esempio nel seguente codice:

 void add_one(int& n) { n += 1; } void add_one(int* const n) { *n += 1; } int main() { int a = 0; add_one(a); // Not clear that a may be modified add_one(&a); // 'a' is clearly being passed destructively } 

Con il puntatore, è sempre (più) ovvio cosa sta succedendo, quindi per le API e simili dove la chiarezza è una grande preoccupazione sono i puntatori non più appropriati dei riferimenti? Ciò significa che i riferimenti dovrebbero essere usati solo quando necessario (ad es. operator++ )? Ci sono problemi di prestazioni con uno o l’altro?

MODIFICA (AGGIORNATO):

Oltre a consentire i valori NULL e la gestione degli array raw, sembra che la scelta dipenda dalle preferenze personali. Ho accettato la risposta qui sotto che fa riferimento alla Guida allo stile C ++ di Google , in quanto presentano la vista secondo cui “I riferimenti possono essere fonte di confusione, poiché hanno una syntax del valore ma la semantica del puntatore.”.

A causa del lavoro aggiuntivo necessario per disinfettare gli argomenti del puntatore che non dovrebbero essere NULL (ad esempio add_one(0) chiamerà la versione del puntatore e si interromperà durante il runtime), ha senso dal punto di vista della manutenibilità usare i riferimenti dove un object DEVE essere presente, sebbene è un peccato perdere la chiarezza sintattica.

Usa la referenza dovunque puoi, i puntatori ovunque tu sia.

Evita i puntatori finché non puoi.

Il motivo è che i puntatori rendono le cose più difficili da seguire / leggere, meno sicure e manipolazioni molto più pericolose di qualsiasi altro costrutto.

Quindi la regola generale è usare i puntatori solo se non c’è altra scelta.

Ad esempio, restituire un puntatore a un object è un’opzione valida quando la funzione può restituire nullptr in alcuni casi e si presume che lo farà. Detto questo, un’opzione migliore sarebbe quella di utilizzare qualcosa di simile a boost::optional .

Un altro esempio è utilizzare i puntatori alla memoria grezza per manipolazioni specifiche della memoria. Questo dovrebbe essere nascosto e localizzato in parti molto strette del codice, per aiutare a limitare le parti pericolose dell’intero codice base.

Nel tuo esempio, non ha senso usare un puntatore come argomento perché:

  1. se fornisci nullptr come argomento, stai andando in undefined-behavior-land;
  2. la versione dell’attributo di riferimento non consente (senza trucchi facili da individuare) il problema con 1.
  3. la versione dell’attributo di riferimento è più semplice da comprendere per l’utente: devi fornire un object valido, non qualcosa che potrebbe essere nullo.

Se il comportamento della funzione dovrebbe funzionare con o senza un dato object, allora usare un puntatore come attributo suggerisce che è ansible passare nullptr come argomento e che va bene per la funzione. È una specie di contratto tra l’utente e l’implementazione.

Le prestazioni sono esattamente le stesse, poiché i riferimenti sono implementati internamente come indicatori. Quindi non devi preoccuparti di questo.

Non esiste una convenzione generalmente accettata in merito a quando utilizzare riferimenti e puntatori. In alcuni casi devi restituire o accettare riferimenti (ad esempio, costruttore di copie), ma a parte questo sei libero di fare ciò che desideri. Una convenzione piuttosto comune che ho incontrato è quella di usare riferimenti quando il parametro deve riferirsi a un object esistente e puntatori quando un valore NULL è ok.

Alcune convenzioni di codifica (come quelle di Google ) prescrivono che si dovrebbero sempre usare puntatori o riferimenti const, perché i riferimenti hanno un po ‘di syntax poco chiara: hanno un comportamento di riferimento ma una syntax del valore.

Da C ++ FAQ Lite –

Usa i riferimenti quando puoi e i puntatori quando devi.

I riferimenti sono generalmente preferiti su puntatori ogni volta che non è necessario “reinserire”. Questo di solito significa che i riferimenti sono più utili nell’interfaccia pubblica di una class. I riferimenti appaiono in genere sulla pelle di un object e puntatori all’interno.

L’eccezione a quanto sopra è dove il parametro o il valore di ritorno di una funzione ha bisogno di un riferimento “sentinella” – un riferimento che non si riferisce a un object. Di solito, è meglio farlo restituendo / prendendo un puntatore e dando al puntatore NULL questo significato speciale (i riferimenti devono sempre essere oggetti alias, non un puntatore NULL dereferenziato).

Nota: i programmatori della linea C a volte non amano i riferimenti poiché forniscono una semantica di riferimento che non è esplicita nel codice del chiamante. Dopo una certa esperienza in C ++, tuttavia, ci si rende subito conto che si tratta di una forma di occultamento delle informazioni, che è una risorsa piuttosto che una responsabilità. Ad esempio, i programmatori dovrebbero scrivere il codice nella lingua del problema piuttosto che nella lingua della macchina.

La mia regola generale è:

  • Utilizzare i puntatori per i parametri di uscita o di entrata / uscita. Quindi si può vedere che il valore sarà cambiato. (Devi usare & )
  • Utilizzare i puntatori se il parametro NULL è un valore accettabile. (Assicurati che sia const se è un parametro in entrata)
  • Utilizzare i riferimenti per il parametro in entrata se non può essere NULL e non è un tipo primitivo ( const T& ).
  • Usa puntatori o puntatori intelligenti quando restituisci un object appena creato.
  • Usa puntatori o puntatori intelligenti come struct o membri della class anziché riferimenti.
  • Utilizza i riferimenti per l’aliasing (ad esempio int &current = someArray[i] )

Indipendentemente da quale utilizzi, non dimenticare di documentare le tue funzioni e il significato dei loro parametri se non sono ovvi.

Come gli altri hanno già risposto: usa sempre riferimenti, a meno che la variabile NULL / nullptr sia davvero uno stato valido.

Il punto di vista di John Carmack sull’argomento è simile:

I puntatori NULL sono il più grande problema in C / C ++, almeno nel nostro codice. Il duplice uso di un singolo valore sia come bandiera che come indirizzo provoca un numero incredibile di problemi fatali. I riferimenti al C ++ dovrebbero essere favoriti su puntatori quando ansible; mentre un riferimento è “veramente” solo un puntatore, ha il contratto implicito di non essere NULL. Esegui controlli NULL quando i puntatori vengono trasformati in riferimenti, quindi puoi ignorare il problema in seguito.

http://www.altdevblogaday.com/2011/12/24/static-code-analysis/

Modifica 2012-03-13

L’utente Bret Kuhns giustamente osserva:

Lo standard C ++ 11 è stato finalizzato. Penso che sia il momento in questo thread per dire che la maggior parte del codice dovrebbe fare perfettamente bene con una combinazione di riferimenti, shared_ptr e unique_ptr.

Abbastanza vero, ma la domanda rimane ancora, anche quando si sostituiscono puntatori grezzi con puntatori intelligenti.

Ad esempio, sia std::unique_ptr che std::shared_ptr possono essere costruiti come puntatori “vuoti” tramite il loro costruttore predefinito:

… il che significa che usarli senza verificare che non siano vuoti rischia di schiantarsi, il che è esattamente ciò che riguarda la discussione di J. Carmack.

E poi, abbiamo il divertente problema di “come si passa un puntatore intelligente come parametro di funzione?”

La risposta di Jon alla domanda C ++ – il passaggio dei riferimenti a boost :: shared_ptr , ei seguenti commenti mostrano che anche in quel caso, passare un puntatore intelligente per copia o per riferimento non è così netto come si vorrebbe (mi favorisco il ” per riferimento “di default, ma potrei sbagliarmi).

Disclaimer: a parte il fatto che i riferimenti non possono essere NULL né “rimbalzo” (il che significa che non possono cambiare l’object di cui sono l’alias), in realtà è una questione di gusti, quindi non ho intenzione di dire “questo è meglio”.

Detto questo, non sono d’accordo con la tua ultima affermazione nel post, in quanto non credo che il codice perda chiarezza con i riferimenti. Nel tuo esempio,

 add_one(&a); 

potrebbe essere più chiaro di

 add_one(a); 

poiché sai che molto probabilmente il valore di a cambierà. D’altra parte, però, la firma della funzione

 void add_one(int* const n); 

in qualche modo non è chiaro: n sarà un singolo intero o un array? A volte hai accesso solo a intestazioni (scarsamente documentate) ea firme simili

 foo(int* const a, int b); 

non sono facili da interpretare a prima vista.

Imho, i riferimenti sono buoni come puntatori quando non è necessario (ri) allocare né ribattere (nel senso spiegato prima). Inoltre, se uno sviluppatore utilizza solo puntatori per gli array, le firme delle funzioni sono in qualche modo meno ambigue. Per non parlare del fatto che la syntax degli operatori è molto più leggibile con i riferimenti.

Non è una questione di gusti. Ecco alcune regole definitive.

Se si desidera fare riferimento a una variabile dichiarata staticamente nell’ambito in cui è stata dichiarata, utilizzare un riferimento C ++ e sarà perfettamente sicuro. Lo stesso vale per un puntatore intelligente dichiarato staticamente. Il passaggio dei parametri per riferimento è un esempio di questo utilizzo.

Se si desidera fare riferimento a qualsiasi cosa proveniente da un ambito che è più ampio dell’ambito in cui è stato dichiarato, è necessario utilizzare un puntatore intelligente conteggiato di riferimento perché sia ​​perfettamente sicuro.

È ansible fare riferimento a un elemento di una raccolta con un riferimento per comodità sintattica, ma non è sicuro; l’elemento può essere cancellato in qualsiasi momento.

Per conservare in sicurezza un riferimento a un elemento di una raccolta, è necessario utilizzare un puntatore intelligente conteggiato di riferimento.

Qualsiasi differenza di prestazioni sarebbe così piccola da non giustificare l’utilizzo di un approccio meno chiaro.

Innanzitutto, un caso che non è stato menzionato in cui i riferimenti sono generalmente superiori sono i riferimenti const . Per i tipi non semplici, il passaggio di un const reference evita di creare un elemento temporaneo e non causa la confusione di cui si è preoccupati (poiché il valore non viene modificato). Qui, forzare una persona a passare un puntatore causa la stessa confusione di cui sei preoccupato, poiché vedere l’indirizzo preso e passato a una funzione potrebbe farti pensare che il valore sia cambiato.

In ogni caso, sostanzialmente sono d’accordo con te. Non mi piacciono le funzioni che prendono riferimenti per modificare il loro valore quando non è molto ovvio che questo è ciò che sta facendo la funzione. Anch’io preferisco usare i puntatori in quel caso.

Quando hai bisogno di restituire un valore in un tipo complesso, tendo a preferire i riferimenti. Per esempio:

 bool GetFooArray(array &foo); // my preference bool GetFooArray(array *foo); // alternative 

Qui, il nome della funzione rende chiaro che stai ricevendo informazioni in un array. Quindi non c’è confusione.

I principali vantaggi dei riferimenti sono che contengono sempre un valore valido, sono più puliti dei puntatori e supportano il polimorfismo senza richiedere alcuna syntax aggiuntiva. Se nessuno di questi vantaggi si applica, non vi è alcun motivo per preferire un riferimento su un puntatore.

Copiato da wiki –

Una conseguenza di ciò è che in molte implementazioni, il funzionamento su una variabile con vita automatica o statica attraverso un riferimento, sebbene sintatticamente simile all’accesso diretto, può implicare operazioni di dereferenziazione nascoste che sono costose. I riferimenti sono una caratteristica sintatticamente controverso del C ++ perché oscurano il livello di indizio di un identificatore; Cioè, a differenza del codice C in cui i puntatori di solito risalgono sintatticamente, in un grande blocco di codice C ++ potrebbe non essere immediatamente ovvio se l’object a cui si accede è definito come variabile locale o globale o se si tratta di un riferimento (puntatore implicito) a qualche altra posizione, specialmente se il codice mescola riferimenti e puntatori. Questo aspetto può rendere più difficile leggere ed eseguire il debug del codice C ++ scritto male (vedi Aliasing).

Sono d’accordo al 100% con questo, ed è per questo che credo che dovresti usare un riferimento solo quando hai una buona ragione per farlo.

C’è un problema con la regola ” usa riferimenti ove ansible ” e si verifica se si desidera mantenere un riferimento per un ulteriore utilizzo. Per illustrare questo con l’esempio, immagina di avere le seguenti lezioni.

 class SimCard { public: explicit SimCard(int id): m_id(id) { } int getId() const { return m_id; } private: int m_id; }; class RefPhone { public: explicit RefPhone(const SimCard & card): m_card(card) { } int getSimId() { return m_card.getId(); } private: const SimCard & m_card; }; 

All’inizio può sembrare una buona idea avere un parametro nel RefPhone(const SimCard & card) passato da un riferimento, poiché impedisce di passare i puntatori errati / nulli al costruttore. In qualche modo incoraggia l’allocazione delle variabili in pila e traendo beneficio da RAII.

 PtrPhone nullPhone(0); //this will not happen that easily SimCard * cardPtr = new SimCard(666); //evil pointer delete cardPtr; //muahaha PtrPhone uninitPhone(cardPtr); //this will not happen that easily 

Ma poi i temporari vengono a distruggere il tuo mondo felice.

 RefPhone tempPhone(SimCard(666)); //evil temporary //function referring to destroyed object tempPhone.getSimId(); //this can happen 

Quindi, se ci si attiene ciecamente ai riferimenti, si scambia la possibilità di passare dei puntatori non validi per la possibilità di memorizzare riferimenti a oggetti distrutti, che ha praticamente lo stesso effetto.

modifica: nota che ho aderito alla regola “Utilizza riferimenti ovunque sia ansible, puntatori ovunque sia necessario. Evita i puntatori finché non puoi”. dalla risposta più upvoted e accettata (altre risposte suggeriscono anche così). Anche se dovrebbe essere ovvio, l’esempio non è mostrare che i riferimenti in quanto tali sono cattivi. Tuttavia possono essere utilizzati in modo improprio, proprio come i puntatori e possono portare le loro minacce al codice.


Ci sono seguenti differenze tra puntatori e riferimenti.

  1. Quando si tratta di passare le variabili, passare per riferimento sembra passare per valore, ma ha una semantica del puntatore (si comporta come un puntatore).
  2. Il riferimento non può essere inizializzato direttamente su 0 (null).
  3. Riferimento (riferimento, object non referenziato) non può essere modificato (equivalente al puntatore “* const”).
  4. il riferimento const può accettare parametri temporanei.
  5. I riferimenti const locali prolungano la durata di oggetti temporanei

Tenendo conto di queste mie regole attuali sono le seguenti.

  • Utilizzare i riferimenti per i parametri che verranno utilizzati localmente nell’ambito di una funzione.
  • Utilizzare i puntatori quando 0 (null) è un valore di parametro accettabile o è necessario memorizzare i parametri per un ulteriore utilizzo. Se 0 (null) è accettabile, aggiungo il suffisso “_n” al parametro, uso il puntatore protetto (come QPointer in Qt) o semplicemente documentarlo. Puoi anche usare puntatori intelligenti. Devi essere ancora più attento con i puntatori condivisi rispetto ai normali puntatori (altrimenti potresti finire con perdite di memoria di progettazione e problemi di responsabilità).

Le seguenti sono alcune linee guida.

Una funzione utilizza i dati passati senza modificarli:

  1. Se l’object dati è piccolo, ad esempio un tipo di dati incorporato o una piccola struttura, passarlo per valore.

  2. Se l’object dati è un array, usa un puntatore perché è la tua unica scelta. Rendi il puntatore un puntatore a const.

  3. Se l’object dati è una struttura di buone dimensioni, utilizzare un puntatore const o un riferimento const per aumentare l’efficienza del programma. Si risparmia il tempo e lo spazio necessari per copiare una struttura o un progetto di class. Crea il puntatore o il riferimento const.

  4. Se l’object dati è un object di class, usa un riferimento const. La semantica del design di class spesso richiede l’uso di un riferimento, che è il motivo principale per cui C ++ ha aggiunto questa funzione. Quindi, il metodo standard per passare gli argomenti object di class è per riferimento.

Una funzione modifica i dati nella funzione di chiamata:

1.Se l’object dati è un tipo di dati incorporato, utilizzare un puntatore. Se si individuano codice come fixit (& x), dove x è un int, è abbastanza chiaro che questa funzione intende modificare x.

2.Se l’object dati è un array, utilizzare l’unica opzione: un puntatore.

3.Se l’object dati è una struttura, utilizzare un riferimento o un puntatore.

4.Se l’object dati è un object di class, utilizzare un riferimento.

Naturalmente, queste sono solo linee guida e potrebbero esserci motivi per fare scelte diverse. Ad esempio, cin usa i riferimenti per i tipi di base in modo da poter usare cin >> n invece di cin >> & n.

Sto solo inserendo la mia moneta. Ho appena fatto un test. Uno sneeky a quello. Ho solo permesso a g ++ di creare i file assembly dello stesso mini-programma usando i puntatori rispetto all’utilizzo dei riferimenti. Guardando l’output sono esattamente gli stessi. Altro che il symbolnaming. Quindi guardando le prestazioni (in un semplice esempio) non ci sono problemi.

Ora sul tema dei puntatori vs riferimenti. IMHO Penso che la chiarezza sia soprattutto. Non appena ho letto un comportamento implicito le dita dei piedi iniziano a raggomitolarsi. Sono d’accordo che è un comportamento implicito bello che un riferimento non può essere NULL.

Dereferenziare un puntatore NULL non è un problema. bloccherà la tua applicazione e sarà facile eseguire il debug. Un problema più grande sono i puntatori non inizializzati contenenti valori non validi. Ciò probabilmente causerà il danneggiamento della memoria causando un comportamento indefinito senza un’origine chiara.

Questo è dove penso che i riferimenti siano molto più sicuri dei puntatori. E sono d’accordo con una precedente affermazione, che l’interfaccia (che dovrebbe essere chiaramente documentata, vedi design per contratto, Bertrand Meyer) definisce il risultato dei parametri in una funzione. Prendendo ora tutto questo in considerazione, le mie preferenze vanno ad usare riferimenti ovunque / quando ansible.

In generale una variabile membro non dovrebbe mai essere un riferimento perché non ha senso in questo. Fa sì che la class non sia assegnabile se non si fornisce un operatore di assegnazione. Inoltre, una volta impostato il riferimento del membro per fare riferimento ad un object, non è ansible cambiarlo per fare riferimento a un altro object. L’utilizzo più appropriato di un riferimento viene utilizzato come parametro di funzione che abilita il passaggio per riferimento.

I riferimenti sono più puliti e più facili da usare e fanno un lavoro migliore per hide le informazioni. I riferimenti non possono essere riassegnati, tuttavia. Se è necessario puntare prima su un object e poi su un altro, è necessario utilizzare un puntatore. I riferimenti non possono essere nulli, quindi se esiste qualche possibilità che l’object in questione possa essere nullo, non è necessario utilizzare un riferimento. Devi usare un puntatore. Se vuoi gestire la manipolazione degli oggetti da solo, cioè se vuoi allocare spazio di memoria per un object sull’Heap piuttosto sullo Stack, devi usare Puntatore

 int *pInt = new int; // allocates *pInt on the Heap 

Per i puntatori, hai bisogno che puntino a qualcosa, quindi i puntatori costano spazio nella memoria.

Ad esempio una funzione che accetta un puntatore intero non prenderà la variabile intera. Quindi dovrai prima creare un puntatore per passare alla funzione.

Per quanto riguarda un riferimento, non costerà la memoria. Hai una variabile intera e puoi passarla come variabile di riferimento. Questo è tutto. Non è necessario creare una variabile di riferimento appositamente per questo.

Punti da tenere a mente:

  1. I puntatori possono essere NULL , i riferimenti non possono essere NULL .

  2. I riferimenti sono più facili da usare, const può essere utilizzato come riferimento quando non vogliamo cambiare valore e serve solo un riferimento in una funzione.

  3. Puntatore utilizzato con i riferimenti * while usati con un & .

  4. Utilizzare i puntatori quando è richiesta l’operazione aritmetica del puntatore.

  5. You can have pointers to a void type int a=5; void *p = &a; but cannot have a reference to a void type.

Pointer Vs Reference

 void fun(int *a) { cout< 

Verdict when to use what

Pointer : For array, linklist, tree implementations and pointer arithmetic.

Reference : In function parameters and return types.

I prefer to use pointers. At least it is clear what you are doing. I have the feeling that references are mostly used because of STL and its syntax implications on code. Because of that also so many C++ standard library novelties like std::move ….. to get exactly what you want, and not what you intuitively would have thought of.

Use references as a last resort. Allocate an instance on the stack or the heap, use them.

Use references for parameter scope to get the least impact. If you use reference because pointers are too hard for you then move to another language.