Puntatore vs. riferimento


256

Quale sarebbe la migliore pratica nel dare a una funzione la variabile originale con cui lavorare:

unsigned long x = 4;

void func1(unsigned long& val) {
     val = 5;            
}
func1(x);

o:

void func2(unsigned long* val) {
     *val = 5;
}
func2(&x);

IOW: C'è qualche motivo per sceglierne uno sopra l'altro?


1
I riferimenti sono ovviamente preziosi, ma vengo da C, dove i puntatori sono ovunque. Uno deve essere abile con i puntatori prima di capire il valore dei riferimenti.
Jay D,

Come si adatta a un obiettivo come la trasparenza referenziale dalla programmazione funzionale? Che cosa succede se si desidera sempre che le funzioni restituiscano nuovi oggetti e non muthino mai internamente lo stato, in particolare non delle variabili passate alla funzione. Esiste un modo in cui questo concetto viene ancora utilizzato con puntatori e riferimenti in un linguaggio come C ++. (Nota, presumo che qualcuno abbia già l'obiettivo della trasparenza referenziale. Non mi interessa parlare se sia o meno un buon obiettivo.)
ely

Preferisci i riferimenti. Puntatori utente quando non hai scelta.
Ferruccio,

Risposte:


285

La mia regola empirica è:

Utilizzare i puntatori se si desidera eseguire l'aritmetica del puntatore con essi (ad esempio incrementando l'indirizzo del puntatore per passare da un array) o se si deve mai passare un puntatore NULL.

Utilizzare i riferimenti altrimenti.


10
Ottimo punto per quanto riguarda un puntatore che è NULL. Se si dispone di un parametro pointer, è necessario verificare esplicitamente che non sia NULL oppure cercare tutti gli utilizzi della funzione per assicurarsi che non sia mai NULL. Questo sforzo non è richiesto per i riferimenti.
Richard Corden,

26
Spiega cosa intendi per aritmetica. Un nuovo utente potrebbe non capire che si desidera regolare ciò a cui punta il puntatore.
Martin York,

7
Martin, per aritmetica intendo che passi un puntatore a una struttura ma sai che non è una struttura semplice ma una matrice di essa. In questo caso puoi indicizzarlo usando [] o fare l'aritmetica usando ++ / - sul puntatore. Questa è la differenza in breve.
Nils Pipenbrinck,

2
Martin, puoi farlo solo con i puntatori direttamente. Non con riferimenti. Sicuramente puoi prendere un puntatore a un riferimento e fare la stessa cosa in pratica, ma se lo fai
finisci

1
Che dire del polimorfismo (ad es. Base* b = new Derived())? Sembra un caso che non può essere gestito senza puntatori.
Chris Redford,

72

Penso davvero che trarrai beneficio dallo stabilire le seguenti funzioni chiamando linee guida per la codifica:

  1. Come in tutti gli altri posti, sii sempre constcorretto.

    • Nota: questo significa, tra le altre cose, che solo i valori out (vedi elemento 3) e i valori passati per valore (vedi elemento 4) possono non avere l' constidentificatore.
  2. Passare un valore tramite puntatore solo se il valore 0 / NULL è un input valido nel contesto corrente.

    • Razionale 1: come chiamante , vedi che qualunque cosa passi deve essere in uno stato utilizzabile.

    • Razionale 2: Come chiamato , sai che qualunque cosa arrivi è in uno stato utilizzabile. Pertanto, non è necessario eseguire il controllo NULL o la gestione degli errori per quel valore.

    • Rationale 3: Rationales 1 e 2 verranno applicati al compilatore . Se riesci, rileva sempre gli errori in fase di compilazione.

  3. Se un argomento di funzione è un valore esterno, passalo per riferimento.

    • Motivazione: Non vogliamo interrompere l'articolo 2 ...
  4. Scegli "passa per valore" su "passa per riferimento const" solo se il valore è un POD ( Plain old Datastructure ) o abbastanza piccolo (dal punto di vista della memoria) o in altri modi abbastanza economico (dal punto di vista del tempo) da copiare.

    • Motivazione: evitare copie non necessarie.
    • Nota: abbastanza piccolo ed economico non sono misurabili assoluti.

Manca la linea guida quando: ... "quando usare const &" ... La linea guida 2 deve essere scritta "per i valori [in], passare solo dal puntatore se NULL è valido. In caso contrario, utilizzare il riferimento const (o per" piccoli "oggetti, copia) o riferimento se si tratta di un valore [out]. Sto monitorando questo post per aggiungere potenzialmente un +1.
paercebal

L'articolo 1 riguarda il caso che descrivi.
Johann Gerell,

È un po 'difficile passare un parametro esterno per riferimento se non è costruibile di default. Questo è abbastanza comune nel mio codice: l'intero motivo per cui una funzione crea quell'oggetto è perché non è banale.
MSalters,

@MSalters: se hai intenzione di allocare la memoria all'interno della funzione (che penso sia ciò che intendi), perché non restituire un puntatore alla memoria allocata?
Kleist,

@Kleist: per conto di @MSalters, ci sono molte possibili ragioni. Uno è che potresti aver già allocato memoria da riempire, come un pre-dimensionato std::vector<>.
Johann Gerell,

24

Questo alla fine finisce per essere soggettivo. La discussione finora è utile, ma non credo che ci sia una risposta corretta o decisiva a questo. Molto dipenderà dalle linee guida di stile e dalle tue esigenze al momento.

Mentre ci sono alcune capacità diverse (se qualcosa può essere NULL) con un puntatore, la differenza pratica più grande per un parametro di output è puramente sintassi. La Guida allo stile C ++ di Google ( https://google.github.io/styleguide/cppguide.html#Reference_Arguments ), ad esempio, impone solo puntatori per i parametri di output e consente solo riferimenti che sono cost. Il ragionamento è di leggibilità: qualcosa con sintassi del valore non dovrebbe avere un significato semantico del puntatore. Non sto suggerendo che questo sia necessariamente giusto o sbagliato, ma penso che il punto qui sia che è una questione di stile, non di correttezza.


Cosa significa che i riferimenti hanno sintassi di valore ma significato semantico del puntatore?
Eric Andrew Lewis,

Sembra che tu stia passando una copia poiché la parte "passa per riferimento" è evidente solo dalla definizione di funzione (sintassi del valore), ma non stai copiando il valore che passi, essenzialmente passi un puntatore sotto il cofano, che consente la funzione per modificare il tuo valore.
phant0m

Non bisogna dimenticare che la guida di stile di Google C ++ è molto detestata.
Deduplicatore il

7

Dovresti passare un puntatore se hai intenzione di modificare il valore della variabile. Anche se tecnicamente passare un riferimento o un puntatore sono gli stessi, passare un puntatore nel tuo caso d'uso è più leggibile in quanto "pubblicizza" il fatto che il valore verrà modificato dalla funzione.


2
Se segui le linee guida di Johann Gerell, un riferimento non const pubblicizza anche una variabile modificabile, quindi il puntatore non ha questo vantaggio qui.
Alexander Kondratskiy,

4
@AlexanderKondratskiy: vi state perdendo il punto ... non si può vedere immediatamente presso il sito di chiamata se la funzione chiamata accetta un paramter come consto non constdi riferimento, ma si può vedere se il parametro del passato ala &xcontro x, e l'uso quella convinzione a codificare se il parametro può essere modificato. (Detto questo, ci sono momenti in cui vorrai passare un constpuntatore, quindi la convensione è solo un suggerimento. Sospettare che qualcosa possa essere modificato quando non sarà meno pericoloso che pensare che non lo sarà quando sarà ....)
Tony Delroy,

5

Se si dispone di un parametro in cui potrebbe essere necessario indicare l'assenza di un valore, è prassi comune rendere il parametro un valore puntatore e passare NULL.

Una soluzione migliore nella maggior parte dei casi (dal punto di vista della sicurezza) è utilizzare boost :: opzionale . Ciò consente di passare valori facoltativi per riferimento e anche come valore di ritorno.

// Sample method using optional as input parameter
void PrintOptional(const boost::optional<std::string>& optional_str)
{
    if (optional_str)
    {
       cout << *optional_str << std::endl;
    }
    else
    {
       cout << "(no string)" << std::endl;
    }
}

// Sample method using optional as return value
boost::optional<int> ReturnOptional(bool return_nothing)
{
    if (return_nothing)
    {
       return boost::optional<int>();
    }

    return boost::optional<int>(42);
}


4

puntatori

  • Un puntatore è una variabile che contiene un indirizzo di memoria.
  • Una dichiarazione del puntatore è composta da un tipo di base, un * e il nome della variabile.
  • Un puntatore può puntare a qualsiasi numero di variabili nel corso della vita
  • A un puntatore che attualmente non punta a una posizione di memoria valida viene assegnato il valore null (che è zero)

    BaseType* ptrBaseType;
    BaseType objBaseType;
    ptrBaseType = &objBaseType;
  • & È un operatore unario che restituisce l'indirizzo di memoria del suo operando.

  • L'operatore di dereferenziazione (*) viene utilizzato per accedere al valore memorizzato nella variabile a cui punta il puntatore.

       int nVar = 7;
       int* ptrVar = &nVar;
       int nVar2 = *ptrVar;

Riferimento

  • Un riferimento (&) è come un alias di una variabile esistente.

  • Un riferimento (&) è come un puntatore costante che viene automaticamente referenziato.

  • Di solito è usato per elenchi di argomenti di funzioni e valori di ritorno di funzioni.

  • Un riferimento deve essere inizializzato quando viene creato.

  • Una volta inizializzato un riferimento a un oggetto, non è possibile modificarlo per fare riferimento a un altro oggetto.

  • Non puoi avere riferimenti NULL.

  • Un riferimento const può fare riferimento a un const int. Viene fatto con una variabile temporanea con valore della const

    int i = 3;    //integer declaration
    int * pi = &i;    //pi points to the integer i
    int& ri = i;    //ri is refers to integer i – creation of reference and initialization

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine


3

Un riferimento è un puntatore implicito. Fondamentalmente puoi cambiare il valore a cui punta il riferimento ma non puoi cambiare il riferimento per puntare a qualcos'altro. Quindi i miei 2 centesimi sono che se vuoi solo cambiare il valore di un parametro passalo come riferimento ma se devi cambiare il parametro per puntare a un oggetto diverso, passalo usando un puntatore.


3

Considera la parola chiave out di C #. Il compilatore richiede al chiamante di un metodo di applicare la parola chiave out a qualsiasi argomento out, anche se sa già se lo sono. Questo ha lo scopo di migliorare la leggibilità. Anche se con gli IDE moderni sono propenso a pensare che questo sia un lavoro per l'evidenziazione della sintassi (o semantica).


errore di battitura: semantico, non simbolico; +1 Concordo sulla possibilità di evidenziare invece di scrivere (C #) o & (in caso di C, nessun riferimento)
peenut

2

Passa per riferimento const a meno che non ci sia una ragione per cui desideri modificare / conservare i contenuti che stai passando.

Questo sarà il metodo più efficiente nella maggior parte dei casi.

Assicurati di usare const su ogni parametro che non desideri modificare, poiché questo non solo ti protegge dal fare qualcosa di stupido nella funzione, ma fornisce una buona indicazione agli altri utenti che cosa fa la funzione ai valori passati. Ciò include la creazione di un puntatore const quando si desidera modificare solo ciò che indica ...


2

puntatori:

  • Può essere assegnato nullptr(oNULL ).
  • Nel sito di chiamata, è necessario utilizzare & se il tipo non è un puntatore stesso, rendendo esplicitamente la modifica dell'oggetto.
  • I puntatori possono essere rimbalzati.

Riferimenti:

  • Non può essere nullo.
  • Una volta associato, non può cambiare.
  • I chiamanti non devono usare esplicitamente &. Questo a volte è considerato negativo perché devi andare all'implementazione della funzione per vedere se il tuo parametro è stato modificato.

Un piccolo punto per coloro che non lo sanno: nullptr o NULL è semplicemente uno 0. stackoverflow.com/questions/462165/…
Serguei Fedorov

2
nullptr non è lo stesso di 0. Prova int a = nullptr; stackoverflow.com/questions/1282295/what-exactly-is-nullptr
Johan Lundberg

0

Un riferimento è simile a un puntatore, tranne per il fatto che non è necessario utilizzare un prefisso ∗ per accedere al valore a cui fa riferimento il riferimento. Inoltre, non è possibile fare riferimento a un oggetto diverso dopo la sua inizializzazione.

I riferimenti sono particolarmente utili per specificare gli argomenti delle funzioni.

per ulteriori informazioni consultare "A Tour of C ++" di "Bjarne Stroustrup" (2014) Pagine 11-12

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.