[c++] Quando utilizzare riferimenti o puntatori


7 Answers

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 oggetto esistente e puntatori quando un valore NULL è ok.

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

Question

Comprendo la sintassi 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 sintassi è 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 sintassi 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 oggetto DEVE essere presente, sebbene è un peccato perdere la chiarezza sintattica.




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 sintassi aggiuntiva. Se nessuno di questi vantaggi si applica, non vi è alcun motivo per preferire un riferimento su un puntatore.




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 possibile; 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 answer 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).




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 possibile.




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 oggetto appena creato.
  • Usa puntatori o puntatori intelligenti come struct o membri della classe 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.




C'è un problema con la regola " usa riferimenti ove possibile " 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 possibile, 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, oggetto 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à).



Preferisco usare i puntatori. 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.




I riferimenti sono più puliti e più facili da usare e fanno un lavoro migliore per nascondere le informazioni. I riferimenti non possono essere riassegnati, tuttavia. Se è necessario puntare prima su un oggetto e poi su un altro, è necessario utilizzare un puntatore. I riferimenti non possono essere nulli, quindi se esiste qualche possibilità che l'oggetto 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 oggetto sull'Heap piuttosto sullo Stack, devi usare Puntatore

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



Related