Ci sono dei vantaggi nel passare per puntatore passando per riferimento in C ++?

Quali sono i vantaggi del passaggio del puntatore al passaggio per riferimento in C ++?

Ultimamente, ho visto un certo numero di esempi che hanno scelto di passare argomenti di funzione tramite puntatori invece di passare per riferimento. Ci sono benefici a fare questo?

Esempio:

func(SPRITE *x); 

con una chiamata di

 func(&mySprite); 

vs.

 func(SPRITE &x); 

con una chiamata di

 func(mySprite); 

Un puntatore può ricevere un parametro NULL, un parametro di riferimento non può. Se c’è sempre la possibilità che tu voglia passare “nessun object”, allora usa un puntatore invece di un riferimento.

Inoltre, passando per puntatore consente di vedere in modo esplicito sul sito di chiamata se l’object viene passato per valore o per riferimento:

 // Is mySprite passed by value or by reference? You can't tell // without looking at the definition of func() func(mySprite); // func2 passes "by pointer" - no need to look up function definition func2(&mySprite); 

Passando da un puntatore

  • Il chiamante deve prendere l’indirizzo -> non trasparente
  • Un valore 0 può essere fornito per nothing significare nothing . Questo può essere usato per fornire argomenti opzionali.

Passare per riferimento

  • Il chiamante passa semplicemente l’object -> trasparente. Deve essere utilizzato per il sovraccarico dell’operatore, poiché l’overloading per i tipi di puntatore non è ansible (i puntatori sono tipi integrati). Quindi non puoi fare string s = &str1 + &str2; usando i puntatori.
  • Nessun valore 0 ansible -> La funzione chiamata non deve controllarli
  • Il riferimento a const accetta anche i temporanei: void f(const T& t); ... f(T(a, b, c)); void f(const T& t); ... f(T(a, b, c)); , i puntatori non possono essere usati in questo modo poiché non puoi prendere l’indirizzo di un temporaneo.
  • Ultimo ma non meno importante, i riferimenti sono più facili da usare -> meno possibilità di bug.

“Enough Rope to Shooth in the Foot” di Allen Holub elenca le seguenti 2 regole:

 120. Reference arguments should always be `const` 121. Never use references as outputs, use pointers 

Elenca diversi motivi per cui i riferimenti sono stati aggiunti a C ++:

  • sono necessari per definire i costruttori di copia
  • sono necessari per sovraccaricare l’operatore
  • const riferimenti const consentono di avere una semantica pass-by-value evitando una copia

Il suo punto principale è che i riferimenti non devono essere usati come parametri di “output” perché al sito di chiamata non c’è indicazione se il parametro è un riferimento o un parametro di valore. Quindi la sua regola è usare solo riferimenti const come argomenti.

Personalmente, penso che questa sia una buona regola empirica in quanto rende più chiaro quando un parametro è un parametro di output o meno. Tuttavia, mentre personalmente sono d’accordo con questo in generale, mi permetto di lasciarmi influenzare dalle opinioni degli altri sulla mia squadra se discutono i parametri di output come riferimenti (alcuni sviluppatori li amano immensamente).

Mi piace il ragionamento di un articolo di “cplusplus.com:”

  1. Passa per valore quando la funzione non vuole modificare il parametro e il valore è facile da copiare (tipi interi, doppi, char, bool, ecc. Semplici. Std :: string, std :: vector e tutti gli altri STL i contenitori NON sono semplici tipi).

  2. Passa con il puntatore const quando il valore è costoso da copiare E la funzione non vuole modificare il valore puntato su AND NULL è un valore valido e previsto che la funzione gestisce.

  3. Passa con puntatore non-const quando il valore è costoso da copiare E la funzione vuole modificare il valore puntato su AND NULL è un valore valido e previsto che la funzione gestisce.

  4. Passa con riferimento const quando il valore è costoso da copiare E la funzione non vuole modificare il valore riferito a AND NULL non sarebbe un valore valido se invece fosse usato un puntatore.

  5. Passare da riferimento non cont quando il valore è costoso da copiare E la funzione vuole modificare il valore riferito a AND NULL non sarebbe un valore valido se invece fosse usato un puntatore.

  6. Quando si scrivono le funzioni template, non c’è una risposta chiara perché ci sono alcuni compromessi da considerare che vanno oltre lo scopo di questa discussione, ma è sufficiente dire che la maggior parte delle funzioni template prende i propri parametri in base al valore o al riferimento (const) tuttavia, poiché la syntax iteratore è simile a quella dei puntatori (asterisco a “dereferenziazione”), anche qualsiasi funzione di modello che si aspetta gli iteratori come argomenti accetta automaticamente anche i puntatori (e non controlla NULL poiché il concetto di iteratore NULL ha una syntax diversa ).

http://www.cplusplus.com/articles/z6vU7k9E/

Quello che prendo da questo è che la principale differenza tra la scelta di utilizzare un puntatore o un parametro di riferimento è se NULL è un valore accettabile. Questo è tutto.

Dopotutto, se il valore è input, output, modificabile ecc. Dovrebbe essere nella documentazione / commenti sulla funzione.

Chiarimenti ai post precedenti:


I riferimenti NON sono una garanzia di ottenere un puntatore non nullo. (Anche se li trattiamo spesso come tali.)

Mentre il codice orribilmente brutto, come nel portarti dietro il codice cattivo della legnaia, il seguente verrà compilato ed eseguito: (Almeno sotto il mio compilatore.)

 bool test( int & a) { return (&a) == (int *) NULL; } int main() { int * i = (int *)NULL; cout << ( test(*i) ) << endl; }; 

Il vero problema che ho con i riferimenti si trova con altri programmatori, d'ora in poi chiamati IDIOTS , che allocano nel costruttore, deallocano nel distruttore e non forniscono un costruttore di copia o operatore = ().

All'improvviso c'è un mondo di differenza tra foo (BAR bar) e foo (BAR e bar) . (Viene richiamata l'operazione di copia bit a bit automatica. La deallocazione in distruttore viene invocata due volte).

Per fortuna i compilatori moderni riprenderanno questa doppia deal dello stesso puntatore. 15 anni fa, non l'hanno fatto. (Sotto gcc / g ++, usa setenv MALLOC_CHECK_ 0 per rivisitare i vecchi modi). Risultante, sotto DEC UNIX, nella stessa memoria assegnata a due oggetti diversi. Un sacco di debug divertente lì ...


Più praticamente:

  • I riferimenti nascondono che si stanno modificando i dati memorizzati altrove.
  • È facile confondere un riferimento con un object copiato.
  • I puntatori rendono ovvio!

Non proprio. Internamente, il passaggio per riferimento viene eseguito essenzialmente passando l’indirizzo dell’object referenziato. Quindi, non ci sono davvero guadagni di efficienza passando un puntatore.

Il passaggio per riferimento ha tuttavia un vantaggio. È garantito avere un’istanza di qualsiasi object / tipo che viene passato. Se si passa un puntatore, si rischia di ricevere un puntatore NULL. Usando il pass-per-riferimento, si sta spingendo un NULL implicito: si verifica un livello fino al chiamante della propria funzione.