Perché i puntatori non sono convertibili in riferimenti?

Ho letto in più fonti che un riferimento C ++ non è altro che un puntatore con restrizioni di tempo compilate.

Se questo è vero, come mai sono costretto a dereferenziare un puntatore per passarlo a una funzione che si aspetta un parametro?

void FooRef(const int&); void FooPointer(const int*); int main() { int* p = new int(5); FooPointer(p); FooRef(*p); // why do I have to dereference the pointer? ... } 

A quanto ho capito, se dovessi passare un int a FooRef il compilatore creerebbe il puntatore (riferimento) dall’indirizzo della variabile per me, ma se il tipo è già un puntatore allora il dereferenziamento sembra inutile. Mi sembra come se stessi denotando un puntatore, solo per lasciare che il compilatore crei un altro puntatore dal valore di riferimento che a me sembra insensato.
Non sarebbe più semplice / più performante copiare semplicemente il puntatore invece di fare riferimento a + derferencing del valore? (Forse questo è davvero quello che sta succedendo?)

Mi sto perdendo qualcosa qui? e chiamare FooRef in uno scenario del genere è più lento che chiamare FooPointer ?
E i riferimenti e i puntatori producono veramente lo stesso codice durante la compilazione?

Il fatto che i riferimenti possano essere implementati in termini di puntatori sotto il cofano è irrilevante. Molti concetti di programmazione possono essere implementati in termini di altre cose. Si può anche chiedere perché abbiamo cicli while può essere implementato in termini di goto o jmp . Il punto di concetti linguistici diversi è quello di rendere le cose più facili per il programmatore, ei riferimenti sono un concetto di linguaggio progettato per la comodità del programmatore.

Probabilmente stai fraintendendo lo scopo dei riferimenti. I riferimenti ti danno il lato positivo dei puntatori (economici da aggirare), ma poiché hanno la stessa semantica dei valori normali, rimuovono molti dei pericoli derivanti dall’uso dei puntatori: (aritmetica dei puntatori, puntatori penzolanti, ecc.) importante, un riferimento è un tipo completamente diverso rispetto a un puntatore nel sistema di tipo C ++, e sarebbe una follia permettere che i due fossero intercambiabili (che vanificherebbe lo scopo dei riferimenti).

La syntax di riferimento è progettata appositamente per rispecchiare la syntax della semantica del valore normale, mentre allo stesso tempo ti fornisce la possibilità di trasferire a basso costo gli indirizzi di memoria invece di copiare interi valori.

Ora, passando al tuo esempio:

 FooRef(*p); // why do I have to dereference the pointer? 

Devi dereferenziare il puntatore qui perché FooRef prende un riferimento a un int , non un riferimento a un int* . Nota che puoi anche avere un riferimento a un puntatore :

 void FooPointerRef(const int*&); 

Una funzione che prende come riferimento un puntatore consente di modificare l’indirizzo di memoria del puntatore all’interno della funzione. Nel tuo esempio, devi dichiarare esplicitamente il puntatore alla semantica del valore di mirroring. Altrimenti, qualcuno che osserva la funzione chiamata FooRef(p) penserà che FooRef prende un puntatore per valore o un puntatore per riferimento – ma NON un valore (non puntatore) o un riferimento.

L’argomento effettivo per un parametro passato per riferimento è in modo coerente lo stesso tipo del parametro, indipendentemente dal fatto che si ottenga assegnando il riferimento a un puntatore o meno e indipendentemente da come viene eseguito il passaggio per riferimento.

Ho letto in più fonti che un riferimento C ++ non è altro che un puntatore con restrizioni di tempo compilate.

Non credere a tutto ciò che leggi.

I puntatori in C ++ sono tipi di dati distinti. Se C ++ avesse forzato un puntatore X * a una X & a un riferimento, come dovrebbe comportarsi il seguente codice?

 int x=5; void* px = (void*)&x; void*& prx = px; 

Inoltre, le cose saranno molto strane – dal momento che quello che vuoi è silenzio dereferenzia, passare NULL comporterà una segmentazione nel codice del chiamante, ma il chiamante non avrà alcun modo sintattico per vederlo.

E cosa otterrai in cambio di questa confusione? Qualche altra confusione tra dettagli di implementazione e astrazioni linguistiche. niente di più.

Beh, ci sono un sacco di risposte qui, basta vedere di persona:

 #include  int main(void) { int p = 5; int *p_ptr = &p; int &p_ref = p; printf("Address of p = %x\n", &p); printf("Address of p_ref = %x\n", &p_ref); printf("Value of p_ptr = %x\n", p_ptr); printf("Address of p_ptr = %x\n", &p_ptr); return 0; } 

Produzione:

 Address of p = 16fd80 Address of p_ref = 16fd80 Value of p_ptr = 16fd80 Address of p_ptr = 16fd88 

Quindi, per quanto riguarda i riferimenti – un riferimento ha lo stesso indirizzo dell’object a cui si fa riferimento. Il valore di un puntatore è l’indirizzo dell’object puntato (o referenziato), pur avendo ancora il proprio indirizzo separato.

Naturalmente, ciò che viene prodotto: ( cl /FA reftest.cpp )

 _TEXT SEGMENT p$ = 32 ; all our variables p_ptr$ = 40 p_ref$ = 48 main PROC ; ... snip ... mov DWORD PTR p$[rsp], 5 ; p = 5 lea rax, QWORD PTR p$[rsp] ; p_ptr = &p mov QWORD PTR p_ptr$[rsp], rax lea rax, QWORD PTR p$[rsp] ; p_ref = p mov QWORD PTR p_ref$[rsp], rax 

Sembra lo stesso per me. Tuttavia, considera questo: ( cl /O2 /FA reftest.cpp )

 _TEXT SEGMENT p$ = 48 p_ptr$ = 56 main PROC ; notice p_ref is gone ; ... snip ... 

La parte migliore dei riferimenti è che sono facili da ottimizzare senza codice. Un riferimento può sempre riferirsi a un object solo durante la sua durata, un puntatore può fare riferimento a molti oggetti diversi durante la sua vita, e il compilatore deve stare attento a questo fatto.

(Nota: Questo è ovviamente solo l’assemblaggio derivante dal compilatore di Microsoft, mentre i risultati possono variare da compilatore a compilatore, sospetto che la maggior parte del compilatore possa estrarre questa funzione)

Creerebbe certamente alcuni casi interessanti e confusi:

 int a(int& x) { return x + 1; } int b(int& x) { return x + 2; } // now for the fun part, 'b' has another valid overload int b(int* x) { return *x + 3; } int myInt = 1; int* p = &myInt; // get a pointer to myInt cout << a(p); // calls int a(int&) by your rule. Syntax error in real C++ cout << b(p); // calls int b(int*), by your rule and real C++. // Isn't that confusing? cout << a(*p); // valid, always calls int a(int&) cout << b(*p); // valid. Always calls b(int&) // This isn't confusing, in real C++ or with your rule 

Sarebbe fonte di confusione se si dovesse costantemente ricordare se fare o meno il dereferenziamento.

L'altro motivo è che semplifica le specifiche. Il compilatore può assumere che un riferimento SEMPRE si riferisce a un object valido. Ottiene le ottimizzazioni basate su tale presupposto. Il modo in cui mantiene tale garanzia è rendere illegale la deferenza del puntatore nullo. Se non avessi dovuto dereferenziare p nell'esempio sopra, allora non potrebbe ottenere quelle garanzie.

Riferimenti e puntatori hanno un comportamento molto simile ad eccezione di quanto segue:

  • I puntatori possono puntare a null, mentre i riferimenti non possono riferirsi a null
  • I riferimenti possono riferirsi a valori che non hanno indirizzi (come i valori memorizzati nei registri). I puntatori devono sempre essere memorizzati, il che può essere più lento.

Potrebbero aver scritto C ++ in cui è ansible convertire automaticamente da un puntatore a un riferimento? Sicuro. Non c'è una vera ragione tecnica per cui non potrebbero avere. Ma gli autori della lingua hanno deciso di fornire più problemi che soluzioni.

C'è un vecchio adagio, "Una lingua non è completa quando hai messo tutto ciò che puoi pensare in esso. Una lingua è completa quando hai preso tutto fuori che è ansible." Molti sostengono che C ++ abbia troppi bagagli da tenere fede a questo adagio, ma ci prova ancora ogni volta che può.