Riferimento vs puntatori di riferimento negli argomenti C ++ / C

3

OK, sto affrontando tutto questo tempo in molte funzioni che scrivo, che dovrei usare?

void sth(int* a)
void sth(int& a)

Quale è più veloce, in due occasioni distinte: a è una piccola variabile o a è una grande struttura di dati.

Vorrei una risposta approfondita con pertinenza all'hardware e allo stack effettivi.

    
posta user209347 20.10.2013 - 11:07
fonte

5 risposte

11

La maggior parte dei compilatori implementerà i riferimenti come puntatori. Quindi la risposta profonda alla tua domanda è che non ci sarà assolutamente alcuna differenza in termini di prestazioni tra i due. (Non cambia l'analisi aliasing per quanto ne so.)

Se vuoi essere sicuro al 100% di questa affermazione, controlla l'output del compilatore.

struct Small {
    int s;
};
void foo(Small* s)
{
    s->s = 1;
}
void bar(Small& s)
{
    s.s = 1;
}

Compilato con clang++ -O2 , salvando l'assembly:

_Z3fooP5Small:                          # @_Z3fooP5Small
    .cfi_startproc
# BB#0:
    movl    $1, (%rdi)
    ret
_Z3barR5Small:                          # @_Z3barR5Small
    .cfi_startproc
# BB#0:
    movl    $1, (%rdi)
    ret

Puoi provarlo con una grande struttura o una struttura enormemente complessa - non importa, tutto quello che stai passando alla funzione è un puntatore.

Detto questo, ci sono semantiche differenze tra i due. Il più importante è che, finché il tuo programma è libero da un comportamento indefinito, il sovraccarico che prende un riferimento è garantito per ottenere un riferimento ad un oggetto live valido. Il sovraccarico del puntatore non lo è.

Anche assegnare a s in questi due esempi ha significati completamente diversi. Sostituirebbe il puntatore nella prima funzione (vale a dire qualsiasi cosa indicasse rimane invariata, ma diventa irraggiungibile da quella funzione, il chiamante non è influenzato dall'assegnazione).
Nel secondo, chiamerebbe l'operatore di assegnazione appropriato nell'oggetto passato (effetto visibile dal chiamante).

Quindi la tua scelta non dovrebbe essere fatta su una potenziale differenza di prestazioni (di solito non ce ne saranno), ma sulla semantica. Ciò di cui hai bisogno per essere in grado di fare la funzione e come dovresti essere in grado di chiamarlo, determinerà quale sovraccarico (i) devi fornire.

    
risposta data 20.10.2013 - 11:23
fonte
10

La differenza di semantica principale tra int* e int& è che il primo permette il passaggio di NULL o valori non inizializzati, e il secondo no. Quindi l'implementazione di una funzione usando i puntatori dovrebbe apparire come questa:

 void sth(int* a)
 {
     if(a==NULL)
     {
         // handle NULL case
     }
     else
     {
         // do something with *a
     }
 }

Quando si usano i riferimenti, è possibile omettere quella speciale gestione NULL all'interno della funzione.

Quindi se la funzione che si sta per scrivere non ha esplicitamente la necessità speciale di consentire i valori NULL come input, utilizzare int& . Vedi anche questa voce di Wikipedia .

Si noti che non si dovrebbe prendere una decisione in base a quale delle 2 alternative è più veloce. La tua prima priorità dovrebbe essere il codice corretto, non le micro-ottimizzazioni, che in questo caso mi aspetterei siano trascurabili.

    
risposta data 20.10.2013 - 11:28
fonte
4

La differenza più importante tra riferimenti e puntatori è che non puoi liberare un oggetto attraverso un riferimento mentre è possibile farlo tramite un puntatore.

Quindi, selezionando il tipo di riferimento anziché il tipo di puntatore per un argomento a in un metodo di un oggetto b pubblicizza che la proprietà di a non viene trasferita a b .

(La credenza comune, che non è possibile passare un NULL dereferenziato come riferimento a un metodo senza imbrogli è errata. La maggior parte dei metodi che creano un oggetto -eg clone o fabbriche-restituirà un puntatore, NULL o no Se il metodo che vuoi chiamare con l'oggetto appena creato usa riferimenti, devi dereferenziare il puntatore.)

    
risposta data 24.10.2013 - 16:00
fonte
1

I riferimenti e i puntatori sono due implementazioni di uno stesso concetto: indirection (cioè "parlare di qualcosa attraverso un pronoun ")

A livello macchina sono la stessa cosa ( indice di una cella di memoria), quindi non ci sono distinzioni prestazionali. A livello linguistico la principale differenza risiede principalmente nell'essere "espliciti" e "mutabili":

  • il dereferenziamento del puntatore è esplicito: dato pa = &a; a.x è lo stesso di pa->x
  • il riferimento alla dereferenziazione è implicito: dato ra = a; a.x è uguale a ra.x

La sintassi identica all'interno delle espressioni rende il riferimento più adatto nelle funzioni generiche, poiché il modo in cui saranno espresse non cambierà se l'accesso alla variabile è diretto o indiretto.

    I puntatori
  • sono mutabili: pa = &a1; ...; pa = &a2; o ++pa o pa[x] sono tutti possibili
  • I riferimenti
  • non sono immutabili: ra = a1; ... ; ra= a2; infatti assegna il valore a2 a a1 (quindi riproduce un gioco diverso)

La natura mutevole dei puntatori li rende più adatti all'implementazione di iteratori generici.

È come parlare di chiavi fisse o adattive. La loro diversa forma rende la loro usabilità cambiare il rispetto in determinati contesti. Ma per quanto riguarda le viti, sono solo chiavi inglesi.

    
risposta data 24.10.2013 - 14:53
fonte
-1

I riferimenti vengono convertiti in puntatori dal compilatore, quindi in fase di esecuzione non c'è differenza di velocità. La velocità non è la ragione per cui scegli i riferimenti anziché i puntatori, ma in realtà i riferimenti potrebbero essere un po 'più veloci nel tuo codice generale semplicemente perché non hai bisogno di controllare costantemente i riferimenti non validi (puntatori NULL).

Se a è una piccola variabile (non più grande della dimensione dell'indirizzo della macchina), allora non c'è differenza tra a e & a . Se a è più grande della dimensione dell'indirizzo della macchina (es. Un oggetto di classe) allora & a è più veloce e & a deve essere fatto ( piuttosto che a ).

Ovviamente devi considerare se vuoi essere in grado di modificare un nella funzione chiamata. Se a è grande e vuoi proibire il cambio di a, passa un riferimento costante a a.

Se usi C ++, dovresti sempre usare riferimenti quando possibile. Un buon libro in C ++ indicherà quale sia la differenza tra riferimenti e puntatori. Dovresti considerare quanto segue:

I riferimenti sono implementati sotto come puntatori. Allora perché usare un riferimento? Perché consente allo scrittore di funzioni di determinare come funziona una funzione senza influire sul modo in cui viene chiamata. Con i riferimenti, il chiamante di una funzione non ha bisogno di sapere se la funzione accetta un puntatore o l'oggetto stesso. Ad esempio, chiamate le 2 funzioni seguenti nello stesso modo e notate che possiamo modificare add1 per usare i riferimenti senza influire su tutti i chiamanti:

int add1 (int a, int b);
int add2 (int &a, int &b) {
// this actually gets converted by the compiler to
// *a + *b
    return a + b;
}

Se stavi usando i puntatori, dovresti sapere che la funzione sta effettivamente prendendo l'indirizzo, non l'oggetto stesso. In altre parole, i riferimenti si aggiungono all'hiding delle informazioni.

Quindi, per riassumere, i riferimenti sono solo zucchero sintattico. Il compilatore convertirà tutti i riferimenti a puntatori e operazioni in riferimenti a operazioni valide con / su puntatori.

In secondo luogo, a differenza dei puntatori - un riferimento è l'oggetto. Con i puntatori, è possibile modificare l'oggetto puntato o è possibile modificare il puntatore stesso (nel qual caso indicherà qualcos'altro). Con un riferimento c'è solo una cosa che puoi cambiare: l'oggetto di riferimento.

Terzo, i riferimenti non possono essere riassegnati per fare riferimento a un altro oggetto come un puntatore. Spesso, nelle liste collegate devi spostare i puntatori avanti / indietro. Non puoi farlo con un riferimento. Pensa a un riferimento come un alias a un oggetto.

Infine, riguardo al NULL ... nel codice C vedrai molto del controllo del puntatore NULL. I riferimenti hanno lo scopo di aiutare con ciò consentendo al compilatore di catturare ogni volta che un riferimento non fa riferimento a un oggetto valido. Lo standard C ++ dice anche che un riferimento non può puntare a un oggetto non valido (ad esempio un NULL). Tuttavia, penso che dipenda dal compilatore su come implementa questo, quindi è necessario ricontrollare il compilatore. Penso che alcuni compilatori non ti avvertiranno nemmeno.

    
risposta data 20.10.2013 - 11:41
fonte

Leggi altre domande sui tag