Quando utilizzare i riferimenti rispetto ai puntatori


381

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

Naturalmente alcune situazioni hanno bisogno dell'una o dell'altra (ha operator++bisogno di un argomento di riferimento), ma in generale sto scoprendo che preferisco usare i puntatori (e i 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? Significa che i riferimenti dovrebbero essere usati solo quando necessario (ad es. operator++)? Ci sono problemi di prestazioni con l'uno o l'altro?

MODIFICA (OUTDATED):

Oltre a consentire valori NULL e gestire array grezzi, sembra che la scelta si riduca alle preferenze personali. Ho accettato la risposta che segue che fa riferimento alla Guida allo stile C ++ di Google , in quanto presentano l'opinione che "I riferimenti possono essere fonte di confusione, poiché hanno una sintassi di valore ma una semantica del puntatore".

A causa del lavoro aggiuntivo richiesto per disinfettare gli argomenti del puntatore che non dovrebbero essere NULL (ad es. add_one(0)Chiamerà la versione del puntatore e si romperà durante il runtime), ha senso dal punto di vista della manutenibilità usare riferimenti in cui un oggetto DEVE essere presente, anche se è un peccato perdere la chiarezza sintattica.


4
Sembra che tu abbia già preso la tua decisione su quale utilizzare quando. Personalmente, preferisco passare l'oggetto su cui sto agendo, indipendentemente dal fatto che lo stia modificando. Se una funzione prende un puntatore, questo mi dice che agisce sui puntatori, cioè usandoli come iteratori in un array.
Benjamin Lindley,

1
@Schnommus: Abbastanza giusto, uso principalmente TextMate. Tuttavia, penso che sia preferibile che il significato sia ovvio da uno sguardo.
connec

4
Che dire di add_one(a);non è chiaro che averrà modificato? Dice proprio nel codice: aggiungine uno .
GManNickG

32
@connec: la guida di stile C ++ di Google non è considerata una buona guida di stile C ++. È una guida di stile per lavorare con la vecchia base di codice C ++ di Google (vale a dire buona per le loro cose). Accettare una risposta basata su ciò non aiuta nessuno. Leggendo i tuoi commenti e le tue spiegazioni sei arrivato a questa domanda con un parere già stabilito e stai solo cercando altre persone per confermare la tua opinione. Di conseguenza stai basando la domanda e la risposta a ciò che vuoi / ti aspetti di sentire.
Martin York,

1
Questo è semplicemente risolto nominando il metodo addOneTo(...). Se non è quello che vuoi fare, guarda la dichiarazione.
stefan,

Risposte:


296

Usa il riferimento dove puoi, i puntatori dove devi.

Evita i puntatori fino a quando non puoi.

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

Quindi la regola empirica è usare i puntatori solo se non c'è altra scelta.

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

Un altro esempio è utilizzare i puntatori alla memoria non elaborata per manipolazioni di memoria specifiche. Questo dovrebbe essere nascosto e localizzato in parti molto strette del codice, per aiutare a limitare le parti pericolose dell'intera base di codice.

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

  1. se fornisci nullptrcome argomento, stai andando in una terra di comportamento indefinito;
  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 capire per l'utente: devi fornire un oggetto valido, non qualcosa che potrebbe essere nullo.

Se il comportamento della funzione dovrebbe funzionare con o senza un determinato oggetto, l'utilizzo di un puntatore come attributo suggerisce che è possibile passare nullptrcome argomento e va bene per la funzione. È una specie di contratto tra l'utente e l'implementazione.


49
Non sono sicuro che i puntatori rendano qualcosa di più difficile da leggere? È un concetto abbastanza semplice e chiarisce quando è probabile che qualcosa venga modificato. Se qualcosa direi che è più difficile da leggere quando non vi è alcuna indicazione di ciò che sta accadendo, perché add_one(a)non dovrebbe restituire il risultato, piuttosto che impostarlo come riferimento?
connec

46
@connec: se add_one(a)è fonte di confusione, è perché ha un nome errato. add_one(&a)avrebbe la stessa confusione, solo ora potresti aumentare il puntatore e non l'oggetto. add_one_inplace(a)eviterebbe ogni confusione.
Nicol Bolas,

20
Un punto, i riferimenti possono fare riferimento alla memoria che può andare via con la stessa facilità dei puntatori. Quindi non sono necessariamente più sicuri dei puntatori. Riferimenti persistenti e di passaggio possono essere pericolosi quanto i puntatori.
Doug T.,

6
@Klaim intendevo puntatori grezzi. Intendevo dire che C ++ ha dei puntatori NULLe nullptrli ha per un motivo. E non è un consiglio ben ponderato o addirittura realistico dare che "non usare mai i puntatori" e / o "non usare mai NULL, usare sempre boost::optional". È semplicemente folle. Non fraintendetemi, i puntatori grezzi sono necessari meno spesso in C ++ che in C, ma sono comunque utili, non sono così "pericolosi" come alcune persone di C ++ amano sostenere (anche questa è un'esagerazione), e ancora: quando è più semplice usare solo un puntatore e return nullptr;indicare un valore mancante ... Perché importare l'intero Boost?

5
@Klaim "usare NULL è una cattiva pratica" - ora è semplicemente ridicolo. Ed ifè obsoleto e si dovrebbe usare while() { break; }invece, giusto? Inoltre, non ti preoccupare, ho visto e lavorato con basi di codice di grandi dimensioni, e sì, se sei negligente , la proprietà è un problema. Non se ti attieni alle convenzioni, usale coerentemente e commenta e documenta il tuo codice, comunque. Ma dopo tutto, dovrei semplicemente usare C perché sono troppo stupido per C ++, giusto?

62

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

Non esiste una convenzione generalmente accettata su quando utilizzare riferimenti e puntatori. In alcuni casi è necessario restituire o accettare riferimenti (ad esempio, costruttore della copia), ma a parte questo si è liberi di fare ciò che si desidera. Una convenzione piuttosto comune che ho riscontrato è quella di utilizzare riferimenti quando il parametro deve fare riferimento a un oggetto 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 sintassi poco chiara: hanno un comportamento di riferimento ma valgono la sintassi.


10
Solo per aggiungere un po 'a questo, la guida di stile di Google afferma che i parametri di input per le funzioni dovrebbero essere riferimenti cost e gli output dovrebbero essere puntatori. Mi piace questo perché rende molto chiaro quando leggi una firma di funzione che cos'è un input e che cos'è un output.
Dan

44
@Dan: la guida di stile di Google è per il vecchio codice di Google e non deve essere utilizzata per la codifica moderna. In effetti, è uno stile di programmazione piuttosto scadente per un nuovo progetto.
GManNickG

13
@connec: Vorrei dirlo in questo modo: null è un valore di puntatore perfettamente valido . Ovunque c'è un puntatore, posso dargli il valore null. Ergo la tua seconda versione add_oneè rotto : add_one(0); // passing a perfectly valid pointer value, Kaboom. Devi verificare se è nullo. Alcune persone replicheranno: "beh, mi limiterò a documentare che la mia funzione non funziona con null". Va bene, ma poi sconfiggi lo scopo della domanda: se hai intenzione di consultare la documentazione per vedere se null è ok, vedrai anche la dichiarazione di funzione .
GManNickG

8
Se fosse un riferimento vedresti che è così. Una replica del genere manca però al punto: i riferimenti fanno valere a livello linguistico che si riferisce a un oggetto esistente, e forse non nullo, mentre i puntatori non hanno tale limitazione. Penso che sia chiaro che l'applicazione a livello di lingua è più potente e meno soggetta a errori rispetto a quella a livello di documentazione. Alcuni tenteranno di replicare a questo dicendo: "Guarda, riferimento null:. int& i = *((int*)0);Questa non è una replica valida. Il problema nel codice precedente risiede nell'uso del puntatore, non nel riferimento . I riferimenti non sono mai nulli, punto.
GManNickG

12
Ciao, ho visto la mancanza di avvocati linguistici nei commenti, quindi lasciami rimediare: i riferimenti sono generalmente implementati da puntatori ma lo standard non dice nulla del genere. Un'implementazione che utilizza qualche altro meccanismo sarebbe una denuncia al 100%.
Thomas Bonini,

34

Da C ++ FAQ Lite -

Usa riferimenti quando puoi e indicazioni quando devi.

I riferimenti sono generalmente preferiti ai puntatori ogni volta che non è necessario il "riposizionamento". Questo di solito significa che i riferimenti sono più utili nell'interfaccia pubblica di una classe. I riferimenti in genere vengono visualizzati sulla skin di un oggetto e i puntatori all'interno.

L'eccezione a quanto sopra è dove il parametro di una funzione o il valore restituito necessita di un riferimento "sentinella" - un riferimento che non si riferisce a un oggetto. Questo di solito viene fatto meglio restituendo / prendendo un puntatore e dando al puntatore NULL questo significato speciale (i riferimenti devono sempre alias oggetti, non un puntatore NULL dereferenziato).

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


1
Immagino che potresti sostenere che se stai usando un'API dovresti avere familiarità con ciò che fa e sapere se il parametro passato viene modificato o meno ... qualcosa da considerare, ma mi trovo d'accordo con i programmatori C ( anche se ho poca esperienza in C). Aggiungerei però che una sintassi più chiara è di beneficio per i programmatori e le macchine.
connec

1
@connec: certo che il programmatore C ha corretto per la loro lingua. Ma non commettere l'errore di trattare C ++ come C. È un linguaggio completamente diverso. Se trattate C ++ come C finirete per scrivere ciò a cui si fa riferimento in modo uguale C with class(che non è C ++).
Martin York,

15

La mia regola empirica è:

  • Utilizzare i puntatori per i parametri in uscita o in / out. Quindi si può vedere che il valore verrà modificato. (Devi usare &)
  • Utilizzare i puntatori se il parametro NULL è un valore accettabile. (Assicurati che sia constse si tratta di un parametro in arrivo)
  • Utilizzare i riferimenti per il parametro in entrata se non può essere NULL e non è un tipo primitivo ( const T&).
  • Utilizzare i puntatori o i puntatori intelligenti quando si restituisce un oggetto appena creato.
  • Utilizzare puntatori o puntatori intelligenti come membri di struttura o di classe anziché come riferimenti.
  • Usa riferimenti per aliasing (ad es. int &current = someArray[i])

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


14

Disclaimer: a parte il fatto che i riferimenti non possono essere NULL né "rimbalzo" (il che significa che non possono cambiare l'oggetto di cui sono alias), si riduce davvero a 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 sta per cambiare. D'altra parte, però, la firma della funzione

void add_one(int* const n);

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

foo(int* const a, int b);

non sono facili da interpretare a prima vista.

Ebbene, i riferimenti sono validi quanto i puntatori quando non è necessaria alcuna (ri) allocazione o rifacimento (nel senso spiegato in precedenza). Inoltre, se uno sviluppatore utilizza solo puntatori per array, le firme delle funzioni sono in qualche modo meno ambigue. Per non parlare del fatto che la sintassi degli operatori è molto più leggibile con i riferimenti.


Grazie per la chiara dimostrazione di dove entrambe le soluzioni ottengono e perdono chiarezza. Inizialmente ero nell'accampamento puntatore, ma questo ha molto senso.
Zach Beavon-Collin,

12

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

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

I puntatori NULL sono il problema più grande in C / C ++, almeno nel nostro codice. Il doppio uso di un singolo valore sia come bandiera che come indirizzo provoca un numero incredibile di problemi fatali. I riferimenti C ++ dovrebbero essere preferiti ai puntatori ogni volta che è possibile; mentre un riferimento è "davvero" solo un puntatore, ha il contratto implicito di non essere NULL. Eseguire i controlli NULL quando i puntatori vengono trasformati in riferimenti, quindi è possibile ignorare il problema in seguito.

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

Modifica 13/03/2012

L'utente Bret Kuhns osserva giustamente:

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

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

Ad esempio, entrambi std::unique_ptre std::shared_ptrpossono essere costruiti come puntatori "vuoti" tramite il loro costruttore predefinito:

... il che significa che usarli senza verificare che non siano vuoti rischia un incidente, ed è esattamente ciò di cui parla la discussione di J. Carmack.

E poi, abbiamo il problema divertente di "come passiamo un puntatore intelligente come parametro di funzione?"

La risposta di Jon per la domanda C ++ - passando riferimenti a boost :: shared_ptr , e i seguenti commenti mostrano che anche allora, passare un puntatore intelligente per copia o per riferimento non è così nitido come si vorrebbe (preferisco me stesso il " per riferimento "per impostazione predefinita, ma potrei sbagliarmi).


1
Lo standard C ++ 11 è stato finalizzato. Penso che sia giunto il momento in questo thread per menzionare che la maggior parte del codice dovrebbe andare perfettamente bene con una combinazione di riferimenti shared_ptr, e unique_ptr. La semantica della proprietà e le convenzioni dei parametri in / out sono curate da una combinazione di questi tre pezzi e costanza. Non c'è quasi bisogno di puntatori grezzi in C ++ tranne quando si tratta di codice legacy e algoritmi molto ottimizzati. Le aree in cui vengono utilizzate dovrebbero essere il più possibile incapsulate e convertire qualsiasi puntatore non elaborato nell'equivalente "moderno" semanticamente appropriato.
Bret Kuhns,

1
Molte volte i puntatori intelligenti non devono essere passati in giro, ma devono essere testati per nullità e quindi il loro oggetto contenuto deve essere passato per riferimento. L'unica volta in cui dovresti effettivamente passare un puntatore intelligente è quando stai trasferendo (unique_ptr) o condividendo (shared_ptr) la proprietà con un altro oggetto.
Luke Vale il

@povman: Sono pienamente d'accordo: se la proprietà non fa parte dell'interfaccia (e se non sta per essere modificata, non dovrebbe esserlo), allora non dovremmo passare un puntatore intelligente come parametro (o valore restituito). La cosa diventa un po 'più complicata quando la proprietà fa parte dell'interfaccia. Ad esempio, il dibattito Sutter / Meyers su come passare un parametro unique_ptr come parametro: dalla copia (Sutter) o dal riferimento del valore r (Meyers)? Un antipattern fa affidamento sul passaggio di un puntatore a un shared_ptr globale, con il rischio che il puntatore venga invalidato (la soluzione sta copiando il puntatore intelligente nello stack)
paercebal,

7

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

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

Se si desidera fare riferimento a qualcosa di un ambito più ampio dell'ambito in cui viene dichiarato, è necessario utilizzare un puntatore intelligente contato di riferimento affinché sia ​​perfettamente sicuro.

Puoi 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 modo sicuro un riferimento a un elemento di una raccolta è necessario utilizzare un puntatore intelligente contato come riferimento.


5

Qualsiasi differenza di prestazione sarebbe così piccola da non giustificare l'uso dell'approccio meno chiaro.

In primo luogo, un caso che non è stato menzionato in cui i riferimenti sono generalmente superiori sono i constriferimenti. Per i tipi non semplici, il passaggio a const referenceevita la creazione di un temporaneo e non provoca confusione (perché il valore non viene modificato). Qui, forzare una persona a passare un puntatore provoca 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, sono sostanzialmente d'accordo con te. Non mi piacciono le funzioni che prendono riferimenti per modificarne il valore quando non è molto ovvio che questo è ciò che la funzione sta facendo. Anche io preferisco usare i puntatori in quel caso.

Quando è necessario 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 chiarisce che stai recuperando 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 bisogno di alcuna sintassi aggiuntiva. Se nessuno di questi vantaggi si applica, non c'è motivo di preferire un riferimento a un puntatore.


4

Copiato dal wiki -

Una conseguenza di ciò è che in molte implementazioni, operare su una variabile con durata automatica o statica attraverso un riferimento, sebbene sintatticamente simile all'accesso diretto ad esso, può comportare operazioni di dereference nascoste che sono costose. I riferimenti sono una caratteristica sintatticamente controversa del C ++ perché oscurano il livello di riferimento indiretto di un identificatore; cioè, a differenza del codice C in cui i puntatori di solito si distinguono sintatticamente, in un grande blocco di codice C ++ potrebbe non essere immediatamente ovvio se l'oggetto a cui si accede è definito come una variabile locale o globale o se è un riferimento (puntatore implicito) a qualche altra posizione, soprattutto se il codice mescola riferimenti e puntatori. Questo aspetto può rendere più difficile la lettura e il debug del codice C ++ scritto male (vedere Aliasing).

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


Sono anche d'accordo in larga misura, tuttavia sto arrivando alla conclusione che la perdita della protezione integrata contro i puntatori NULL è un po 'troppo costosa per preoccupazioni puramente sintattiche, soprattutto perché - sebbene più esplicita - la sintassi del puntatore è piuttosto brutta Comunque.
connec

Suppongo che anche la circostanza sarebbe un fattore importante. Penso che provare a usare i riferimenti quando l'attuale base di codice utilizza prevalentemente i puntatori sarebbe una cattiva idea. Se ti aspetti che siano riferimenti allora il fatto che il loro così implicito sia meno importante forse ..
user606723

3

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, constpossono essere usati come riferimento quando non vogliamo cambiare valore e abbiamo solo bisogno di un riferimento in una funzione.

  3. Puntatore usato con un *po 'di riferimenti usati con a &.

  4. Utilizzare i puntatori quando sono necessarie operazioni aritmetiche con i puntatori.

  5. È possibile disporre di puntatori a un tipo di vuoto int a=5; void *p = &a;ma non è possibile avere un riferimento a un tipo di vuoto.

Puntatore Vs Riferimento

void fun(int *a)
{
    cout<<a<<'\n'; // address of a = 0x7fff79f83eac
    cout<<*a<<'\n'; // value at a = 5
    cout<<a+1<<'\n'; // address of a increment by 4 bytes(int) = 0x7fff79f83eb0
    cout<<*(a+1)<<'\n'; // value here is by default = 0
}
void fun(int &a)
{
    cout<<a<<'\n'; // reference of original a passed a = 5
}
int a=5;
fun(&a);
fun(a);

Verdetto su quando usare cosa

Puntatore : per array, elenco di collegamenti, implementazioni dell'albero e aritmetica del puntatore.

Riferimento : nei parametri di funzione e nei tipi di ritorno.


2

Vi è un problema con la regola " utilizza riferimenti ove possibile " e si presenta se si desidera conservare i riferimenti per un ulteriore utilizzo. Per illustrare questo con l'esempio, immagina di avere le seguenti classi.

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)costruttore passato da un riferimento, perché impedisce al costruttore di passare puntatori errati / nulli. Incoraggia in qualche modo l'allocazione delle variabili in pila e trarre vantaggio dalla 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 provvisori vengono per 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 attacca ciecamente a riferimenti, si compromette la possibilità di passare puntatori non validi per la possibilità di memorizzare riferimenti a oggetti distrutti, che ha sostanzialmente lo stesso effetto.

modifica: nota che mi sono attenuto alla regola "Usa il riferimento dove puoi, i puntatori dove devi. Evita i puntatori finché non puoi". dalla risposta più votata e accettata (anche altre risposte lo suggeriscono). Sebbene dovrebbe essere ovvio, l'esempio non è quello di mostrare che i riferimenti in quanto tali sono cattivi. Tuttavia, possono essere utilizzati in modo improprio, proprio come i puntatori e possono portare le proprie minacce al codice.


Esistono le seguenti differenze tra puntatori e riferimenti.

  1. Quando si tratta di passare 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. Il riferimento (riferimento, oggetto non referenziato) non può essere modificato (equivalente al puntatore "* const").
  4. il riferimento const può accettare un parametro temporaneo.
  5. I riferimenti const locali prolungano la durata degli oggetti temporanei

Tenendo conto di queste, le 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, sto aggiungendo il suffisso "_n" al parametro, utilizzare il puntatore protetto (come QPointer in Qt) o semplicemente documentarlo. Puoi anche usare i puntatori intelligenti. Devi essere ancora più attento con i puntatori condivisi che con i puntatori normali (altrimenti puoi finire col progettare perdite di memoria e pasticcio di responsabilità).

3
Il problema con il tuo esempio non è che i riferimenti non sono sicuri, ma che ti affidi a qualcosa al di fuori dell'ambito dell'istanza dell'oggetto per mantenere in vita i membri privati. const SimCard & m_card;è solo un codice scritto male.
plamenko,

@plamenko Temo che tu non capisca lo scopo dell'esempio. La const SimCard & m_cardcorrettezza o meno dipende dal contesto. Il messaggio in questo post non è che i riferimenti non siano sicuri (anche se possono esserlo se uno si impegna duramente). Il messaggio è che non si dovrebbe attenersi ciecamente al mantra "usare riferimenti ogni volta che è possibile". L'esempio è un risultato dell'uso aggressivo della dottrina "usa riferimenti ogni volta che è possibile". Questo dovrebbe essere chiaro.
doc

Ci sono due cose che mi infastidiscono con la tua risposta perché penso che possa indurre in errore qualcuno che cerca di saperne di più sull'argomento. 1. Il post è unidirezionale ed è facile avere l'impressione che i riferimenti siano sbagliati. Hai fornito solo un singolo esempio di come non utilizzare i riferimenti. 2. Nel tuo esempio non eri chiaro cosa ci fosse di sbagliato. Sì, il temporaneo verrà distrutto, ma non era quella linea che era sbagliata, è l'implementazione della classe.
Plamenko,

Praticamente non dovresti mai avere membri come const SimCard & m_card. Se vuoi essere efficiente con i provvisori, aggiungi il explicit RefPhone(const SimCard&& card)costruttore.
Plamenko,

@plamenko se non riesci a leggere con una comprensione di base, allora hai un problema più grande che essere semplicemente fuorviato dal mio post. Non so come potrei essere più chiaro. Guarda la prima frase. C'è un problema con il mantra "usa riferimenti ogni volta che è possibile"! Dove nel mio post hai trovato una dichiarazione secondo cui i riferimenti sono sbagliati? Alla fine del mio post hai scritto dove utilizzare i riferimenti, quindi come sei arrivato a tali conclusioni? Questa non è una risposta diretta alla domanda?
doc

1

Di seguito sono riportate alcune linee guida.

Una funzione utilizza i dati passati senza modificarli:

  1. Se l'oggetto dati è di piccole dimensioni, ad esempio un tipo di dati incorporato o una piccola struttura, passalo per valore.

  2. Se l'oggetto dati è un array, utilizzare un puntatore perché è l'unica scelta. Trasforma il puntatore in un puntatore a const.

  3. Se l'oggetto 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 classe. Crea il puntatore o la cost. Di riferimento

  4. Se l'oggetto dati è un oggetto di classe, utilizzare un riferimento const. La semantica del design della classe spesso richiede l'uso di un riferimento, che è la ragione principale per cui C ++ ha aggiunto questa funzione. Pertanto, il modo standard per passare argomenti di oggetti di classe è per riferimento.

Una funzione modifica i dati nella funzione chiamante:

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

2. Se l'oggetto dati è un array, utilizzare l'unica scelta: un puntatore.

3.Se l'oggetto dati è una struttura, utilizzare un riferimento o un puntatore.

4. Se l'oggetto dati è un oggetto di classe, utilizzare un riferimento.

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


0

I riferimenti sono più puliti e più facili da usare e svolgono un lavoro migliore nel nascondere le informazioni. Tuttavia, i riferimenti non possono essere riassegnati. Se devi prima puntare su un oggetto e poi su un altro, devi usare 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. È necessario utilizzare un puntatore. Se si desidera gestire da soli la manipolazione di oggetti, ad esempio se si desidera allocare spazio di memoria per un oggetto nell'heap anziché nello stack, è necessario utilizzare il puntatore

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

0

Dovresti avere un esempio scritto correttamente

void add_one(int& n) { n += 1; }
void add_one(int* const n)
{
  if (n)
    *n += 1;
}

Ecco perché i riferimenti sono preferibili se possibile ...


-1

Sto solo mettendo il mio centesimo. Ho appena eseguito un test. Uno sneeky a quello. Ho appena lasciato che g ++ creasse i file assembly dello stesso mini-programma usando i puntatori rispetto all'utilizzo dei riferimenti. Quando si guarda all'output sono esattamente gli stessi. Altro che simboli. Quindi, guardando le prestazioni (in un semplice esempio) non ci sono problemi.

Ora sull'argomento puntatori e riferimenti. Secondo me la chiarezza sta soprattutto. Non appena leggo il comportamento implicito le dita dei piedi iniziano ad arricciarsi. Sono d'accordo che è un comportamento implicito piacevole che un riferimento non possa essere NULL.

Dereferenziare un puntatore NULL non è il problema. si bloccherà l'applicazione e sarà facile eseguire il debug. Un problema più grande sono i puntatori non inizializzati contenenti valori non validi. Ciò molto probabilmente comporterà il danneggiamento della memoria causando comportamenti indefiniti senza un'origine chiara.

È qui che penso che i riferimenti siano molto più sicuri dei puntatori. E sono d'accordo con una precedente dichiarazione, che l'interfaccia (che dovrebbe essere chiaramente documentata, vedi progetto per contratto, Bertrand Meyer) definisce il risultato dei parametri in una funzione. Ora, tenendo conto di tutto ciò, le mie preferenze vanno all'utilizzo di riferimenti dove / quando possibile.


-2

Per i puntatori, è necessario che puntino a qualcosa, quindi i puntatori costano spazio di memoria.

Ad esempio, una funzione che accetta un puntatore intero non prenderà la variabile intera. Quindi sarà necessario creare un puntatore affinché questo passi prima alla funzione.

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


4
No. Una funzione che accetta un puntatore non richiede l'allocazione di una variabile puntatore: puoi passare un temporaneo &address. Un riferimento sicuramente costerà memoria se è un membro di un oggetto e, inoltre, tutti i compilatori esistenti implementano effettivamente i riferimenti come indirizzi, quindi non si risparmia nulla in termini di passaggio di parametri o di dereferenziazione.
underscore_d
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.