[c++] Quali sono le differenze tra una variabile puntatore e una variabile di riferimento in C ++?


14 Answers

Cos'è un riferimento C ++ ( per i programmatori C )

Un riferimento può essere pensato come un puntatore costante (da non confondere con un puntatore a un valore costante!) Con l'indirezione automatica, cioè il compilatore applicherà l'operatore * per te.

Tutti i riferimenti devono essere inizializzati con un valore non nullo o la compilazione avrà esito negativo. Non è possibile ottenere l'indirizzo di un riferimento - l'operatore di indirizzo restituirà invece l'indirizzo del valore di riferimento - né è possibile fare aritmetica sui riferimenti.

I programmatori C potrebbero non gradire i riferimenti C ++ in quanto non sarà più ovvio quando si verifica indiretta o se un argomento viene passato per valore o per puntatore senza guardare le firme della funzione.

Ai programmatori C ++ potrebbe non piacere usare i puntatori perché sono considerati non sicuri - sebbene i riferimenti non siano realmente più sicuri dei puntatori costanti tranne nei casi più banali - mancano della convenienza dell'indirizzamento automatico e portano una diversa connotazione semantica.

Considera la seguente dichiarazione dalle FAQ del C ++ :

Anche se un riferimento viene spesso implementato utilizzando un indirizzo nella lingua dell'assembly sottostante, non pensare a un riferimento come a un puntatore dall'aspetto divertente a un oggetto. Un riferimento è l'oggetto. Non è un puntatore all'oggetto, né una copia dell'oggetto. È l'oggetto

Ma se un riferimento fosse davvero l'oggetto, come potrebbero esserci riferimenti ciondolanti? Nei linguaggi non gestiti, è impossibile che i riferimenti siano "più sicuri" dei puntatori: in genere non è un modo per fare in modo affidabile l'alias dei valori attraverso i limiti del campo di applicazione!

Perché considero utili i riferimenti C ++

Provenienti da uno sfondo C, i riferimenti C ++ possono sembrare un concetto un po 'sciocco, ma dovremmo comunque usarli al posto dei puntatori laddove possibile: l'indirezione automatica è conveniente e i riferimenti diventano particolarmente utili quando si ha a che fare con RAII - ma non a causa di alcuna sicurezza percepita vantaggio, ma piuttosto perché rendono la scrittura del codice idiomatico meno imbarazzante.

RAII è uno dei concetti centrali del C ++, ma interagisce in modo non banale con la semantica della copia. Passare gli oggetti per riferimento evita questi problemi poiché non è coinvolta alcuna copia. Se i riferimenti non fossero presenti nella lingua, dovresti invece usare dei puntatori, che sono più complicati da usare, violando così il principio del linguaggio design che la soluzione delle migliori pratiche dovrebbe essere più semplice delle alternative.

Question

So che i riferimenti sono zucchero sintattico, quindi il codice è più facile da leggere e scrivere.

Ma quali sono le differenze?

Riepilogo delle risposte e dei collegamenti seguenti:

  1. Un puntatore può essere riassegnato un numero qualsiasi di volte mentre un riferimento non può essere ri-inserito dopo l'associazione.
  2. I puntatori non possono puntare da nessuna parte ( NULL ), mentre il riferimento si riferisce sempre a un oggetto.
  3. Non puoi prendere l'indirizzo di un riferimento come puoi con i puntatori.
  4. Non c'è "aritmetica di riferimento" (ma puoi prendere l'indirizzo di un oggetto puntato da un riferimento e fare aritmetica puntatore su di esso come in &obj + 5 ).

Per chiarire un equivoco:

Lo standard C ++ è molto attento a evitare di dettare come un compilatore deve implementare riferimenti, ma ogni compilatore C ++ implementa riferimenti come puntatori. Cioè, una dichiarazione come:

int &ri = i;

se non è ottimizzato completamente , assegna la stessa quantità di memoria di un puntatore e inserisce l'indirizzo di i in tale archivio.

Quindi, un puntatore e un riferimento occupano entrambi la stessa quantità di memoria.

Come regola generale,

  • Utilizzare i riferimenti nei parametri di funzione e i tipi restituiti per definire interfacce utili e autodocumentanti.
  • Utilizzare i puntatori per implementare algoritmi e strutture dati.

Lettura interessante:




It doesn't matter how much space it takes up since you can't actually see any side effect (without executing code) of whatever space it would take up.

On the other hand, one major difference between references and pointers is that temporaries assigned to const references live until the const reference goes out of scope.

Per esempio:

class scope_test
{
public:
    ~scope_test() { printf("scope_test done!\n"); }
};

...

{
    const scope_test &test= scope_test();
    printf("in scope\n");
}

stamperà:

in scope
scope_test done!

This is the language mechanism that allows ScopeGuard to work.




A parte lo zucchero sintattico, un riferimento è un puntatore const ( non un puntatore a un const ). È necessario stabilire a cosa si riferisce quando si dichiara la variabile di riferimento e non è possibile modificarla in seguito.

Aggiornamento: ora che ci penso un po 'di più, c'è una differenza importante.

Il bersaglio di un puntatore const può essere sostituito prendendo il suo indirizzo e usando un cast const.

L'obiettivo di un riferimento non può essere sostituito in alcun modo a corto di UB.

Questo dovrebbe permettere al compilatore di fare più ottimizzazione su un riferimento.




Another difference is that you can have pointers to a void type (and it means pointer to anything) but references to void are forbidden.

int a;
void * p = &a; // ok
void & p = a;  //  forbidden

I can't say I'm really happy with this particular difference. I would much prefer it would be allowed with the meaning reference to anything with an address and otherwise the same behavior for references. It would allow to define some equivalents of C library functions like memcpy using references.




A reference to a pointer is possible in C++, but the reverse is not possible means a pointer to a reference isn't possible. A reference to a pointer provides a cleaner syntax to modify the pointer. Look at this example:

#include<iostream>
using namespace std;

void swap(char * &str1, char * &str2)
{
  char *temp = str1;
  str1 = str2;
  str2 = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap(str1, str2);
  cout<<"str1 is "<<str1<<endl;
  cout<<"str2 is "<<str2<<endl;
  return 0;
}

And consider the C version of the above program. In C you have to use pointer to pointer (multiple indirection), and it leads to confusion and the program may look complicated.

#include<stdio.h>
/* Swaps strings by swapping pointers */
void swap1(char **str1_ptr, char **str2_ptr)
{
  char *temp = *str1_ptr;
  *str1_ptr = *str2_ptr;
  *str2_ptr = temp;
}

int main()
{
  char *str1 = "Hi";
  char *str2 = "Hello";
  swap1(&str1, &str2);
  printf("str1 is %s, str2 is %s", str1, str2);
  return 0;
}

Visit the following for more information about reference to pointer:

As I said, a pointer to a reference isn't possible. Try the following program:

#include <iostream>
using namespace std;

int main()
{
   int x = 10;
   int *ptr = &x;
   int &*ptr1 = ptr;
}



At the risk of adding to confusion, I want to throw in some input, I'm sure it mostly depends on how the compiler implements references, but in the case of gcc the idea that a reference can only point to a variable on the stack is not actually correct, take this for example:

#include <iostream>
int main(int argc, char** argv) {
    // Create a string on the heap
    std::string *str_ptr = new std::string("THIS IS A STRING");
    // Dereference the string on the heap, and assign it to the reference
    std::string &str_ref = *str_ptr;
    // Not even a compiler warning! At least with gcc
    // Now lets try to print it's value!
    std::cout << str_ref << std::endl;
    // It works! Now lets print and compare actual memory addresses
    std::cout << str_ptr << " : " << &str_ref << std::endl;
    // Exactly the same, now remember to free the memory on the heap
    delete str_ptr;
}

Which outputs this:

THIS IS A STRING
0xbb2070 : 0xbb2070

If you notice even the memory addresses are exactly the same, meaning the reference is successfully pointing to a variable on the heap! Now if you really want to get freaky, this also works:

int main(int argc, char** argv) {
    // In the actual new declaration let immediately de-reference and assign it to the reference
    std::string &str_ref = *(new std::string("THIS IS A STRING"));
    // Once again, it works! (at least in gcc)
    std::cout << str_ref;
    // Once again it prints fine, however we have no pointer to the heap allocation, right? So how do we free the space we just ignorantly created?
    delete &str_ref;
    /*And, it works, because we are taking the memory address that the reference is
    storing, and deleting it, which is all a pointer is doing, just we have to specify
    the address with '&' whereas a pointer does that implicitly, this is sort of like
    calling delete &(*str_ptr); (which also compiles and runs fine).*/
}

Which outputs this:

THIS IS A STRING

Therefore a reference IS a pointer under the hood, they both are just storing a memory address, where the address is pointing to is irrelevant, what do you think would happen if I called std::cout << str_ref; AFTER calling delete &str_ref? Well, obviously it compiles fine, but causes a segmentation fault at runtime because it's no longer pointing at a valid variable, we essentially have a broken reference that still exists (until it falls out of scope), but is useless.

In other words, a reference is nothing but a pointer that has the pointer mechanics abstracted away, making it safer and easier to use (no accidental pointer math, no mixing up '.' and '->', etc.), assuming you don't try any nonsense like my examples above ;)

Now regardless of how a compiler handles references, it will always have some kind of pointer under the hood, because a reference must refer to a specific variable at a specific memory address for it to work as expected, there is no getting around this (hence the term 'reference').

The only major rule that's important to remember with references is that they must be defined at the time of declaration (with the exception of a reference in a header, in that case it must be defined in the constructor, after the object it's contained in is constructed it's too late to define it).

Remember, my examples above are just that, examples demonstrating what a reference is, you would never want to use a reference in those ways! For proper usage of a reference there are plenty of answers on here already that hit the nail on the head




Both references and pointers can be used to change local variables of one function inside another function. Both of them can also be used to save copying of big objects when passed as arguments to functions or returned from functions, to get efficiency gain. Despite above similarities, there are following differences between references and pointers.

References are less powerful than pointers

1) Una volta creato un riferimento, non può essere successivamente fatto per fare riferimento a un altro oggetto; non può essere reinserito. Questo è spesso fatto con i puntatori.

2) I riferimenti non possono essere NULL. I puntatori vengono spesso resi NULL per indicare che non stanno puntando a nessuna cosa valida.

3) Un riferimento deve essere inizializzato quando dichiarato. Non esiste una tale restrizione con i puntatori

Due to the above limitations, references in C++ cannot be used for implementing data structures like Linked List, Tree, etc. In Java, references don't have above restrictions, and can be used to implement all data structures. References being more powerful in Java, is the main reason Java doesn't need pointers.

I riferimenti sono più sicuri e più facili da usare:

1) Più sicuro: poiché i riferimenti devono essere inizializzati, è improbabile che esistano riferimenti selvaggi come puntatori selvaggi. È ancora possibile avere riferimenti che non si riferiscono a una posizione valida

2) Più facile da usare: i riferimenti non richiedono l'operatore di dereferenziamento per accedere al valore. Possono essere usati come normali variabili. L'operatore '&' è necessario solo al momento della dichiarazione. Inoltre, è possibile accedere ai membri di un riferimento a un oggetto con l'operatore punto ('.'), Diversamente dai puntatori in cui è necessario l'operatore di freccia (->) per accedere ai membri.

Together with the above reasons, there are few places like copy constructor argument where pointer cannot be used. Reference must be used pass the argument in copy constructor. Similarly references must be used for overloading some operators like ++ .




A reference is an alias for another variable whereas a pointer holds the memory address of a variable. References are generally used as function parameters so that the passed object is not the copy but the object itself.

    void fun(int &a, int &b); // A common usage of references.
    int a = 0;
    int &b = a; // b is an alias for a. Not so common to use. 



Mentre sia i riferimenti che i puntatori vengono utilizzati per accedere indirettamente a un altro valore, esistono due importanti differenze tra riferimenti e puntatori. Il primo è che un riferimento fa sempre riferimento a un oggetto: è un errore definire un riferimento senza inizializzarlo. Il comportamento dell'assegnazione è la seconda importante differenza: l'assegnazione a un riferimento modifica l'oggetto a cui è associato il riferimento; non ricollega il riferimento a un altro oggetto. Una volta inizializzato, un riferimento si riferisce sempre allo stesso oggetto sottostante.

Considera questi due frammenti di programma. Nel primo, assegniamo un puntatore a un altro:

int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;    // pi now points to ival2

Dopo l'assegnazione, ival, l'oggetto indirizzato da pi rimane invariato. L'assegnazione cambia il valore di pi, facendo in modo che punti a un oggetto diverso. Now consider a similar program that assigns two references:

int &ri = ival, &ri2 = ival2;
ri = ri2;    // assigns ival2 to ival

This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.




I have an analogy for references and pointers, think of references as another name for an object and pointers as the address of an object.

// receives an alias of an int, an address of an int and an int value
public void my_function(int& a,int* b,int c){
    int d = 1; // declares an integer named d
    int &e = d; // declares that e is an alias of d
    // using either d or e will yield the same result as d and e name the same object
    int *f = e; // invalid, you are trying to place an object in an address
    // imagine writting your name in an address field 
    int *g = f; // writes an address to an address
    g = &d; // &d means get me the address of the object named d you could also
    // use &e as it is an alias of d and write it on g, which is an address so it's ok
}



Contrariamente all'opinione comune, è possibile avere un riferimento che è NULL.

int * p = NULL;
int & r = *p;
r = 1;  // crash! (if you're lucky)

Certo, è molto più difficile da fare con un riferimento - ma se lo gestisci, ti strapperai i capelli cercando di trovarlo. I riferimenti non sono intrinsecamente sicuri in C ++!

Tecnicamente questo è un riferimento non valido , non un riferimento null. Il C ++ non supporta i riferimenti null come concetto che potresti trovare in altre lingue. Esistono anche altri tipi di riferimenti non validi. Qualsiasi riferimento non valido solleva lo spettro del comportamento non definito , proprio come farebbe con un puntatore non valido.

L'errore effettivo è nel dereferenziamento del puntatore NULL, prima dell'assegnazione a un riferimento. Ma non sono a conoscenza di alcun compilatore che genererà errori su tale condizione - l'errore si propagherà in un punto più avanti nel codice. Questo è ciò che rende questo problema così insidioso. La maggior parte delle volte, se si denota un puntatore NULL, si blocca proprio in quel punto e non ci vuole molto a fare il debug per capirlo.

Il mio esempio sopra è breve e forzato. Ecco un esempio più realistico.

class MyClass
{
    ...
    virtual void DoSomething(int,int,int,int,int);
};

void Foo(const MyClass & bar)
{
    ...
    bar.DoSomething(i1,i2,i3,i4,i5);  // crash occurs here due to memory access violation - obvious why?
}

MyClass * GetInstance()
{
    if (somecondition)
        return NULL;
    ...
}

MyClass * p = GetInstance();
Foo(*p);

Voglio ribadire che l'unico modo per ottenere un riferimento null è attraverso il codice non valido e, una volta ottenuto, si ottiene un comportamento indefinito. Non ha mai senso verificare un riferimento null; per esempio puoi provare if(&bar==NULL)... ma il compilatore potrebbe ottimizzare l'affermazione fuori dall'esistenza! Un riferimento valido non può mai essere NULL, quindi dal punto di vista del compilatore il confronto è sempre falso ed è libero di eliminare la clausola if come codice morto - questa è l'essenza del comportamento non definito.

Il modo corretto per evitare problemi consiste nell'evitare il dereferenziamento di un puntatore NULL per creare un riferimento. Ecco un modo automatico per realizzare questo.

template<typename T>
T& deref(T* p)
{
    if (p == NULL)
        throw std::invalid_argument(std::string("NULL reference"));
    return *p;
}

MyClass * p = GetInstance();
Foo(deref(p));

Per uno sguardo più vecchio a questo problema da qualcuno con migliori capacità di scrittura, vedi Null References di Jim Hyslop e Herb Sutter.

Per un altro esempio dei pericoli del dereferenziamento, un puntatore nullo può vedere Esporre un comportamento indefinito quando si tenta di portare il codice su un'altra piattaforma di Raymond Chen.




I riferimenti sono molto simili ai puntatori, ma sono creati appositamente per essere utili all'ottimizzazione dei compilatori.

  • I riferimenti sono progettati in modo tale che è sostanzialmente più semplice per il compilatore tracciare quali alias di riferimento quali variabili. Due importanti caratteristiche sono molto importanti: nessuna "aritmetica di riferimento" e nessuna riassegnazione di riferimenti. Questi permettono al compilatore di capire quali riferimenti alias quali variabili in fase di compilazione.
  • I riferimenti possono riferirsi a variabili che non hanno indirizzi di memoria, come quelli che il compilatore sceglie di inserire nei registri. Se si prende l'indirizzo di una variabile locale, è molto difficile per il compilatore inserirlo in un registro.

Come esempio:

void maybeModify(int& x); // may modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // This function is designed to do something particularly troublesome
    // for optimizers. It will constantly call maybeModify on array[0] while
    // adding array[1] to array[2]..array[size-1]. There's no real reason to
    // do this, other than to demonstrate the power of references.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(array[0]);
        array[i] += array[1];
    }
}

Un compilatore ottimizzante potrebbe rendersi conto che stiamo accedendo a [0] e a [1] un bel po '. Mi piacerebbe ottimizzare l'algoritmo per:

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Do the same thing as above, but instead of accessing array[1]
    // all the time, access it once and store the result in a register,
    // which is much faster to do arithmetic with.
    register int a0 = a[0];
    register int a1 = a[1]; // access a[1] once
    for (int i = 2; i < (int)size; i++) {
        maybeModify(a0); // Give maybeModify a reference to a register
        array[i] += a1;  // Use the saved register value over and over
    }
    a[0] = a0; // Store the modified a[0] back into the array
}

Per fare una tale ottimizzazione, è necessario dimostrare che nulla può cambiare array [1] durante la chiamata. Questo è piuttosto facile da fare. non è mai inferiore a 2, quindi array [i] non può mai fare riferimento a array [1]. mayModify () è dato a0 come riferimento (aliasing array [0]). Poiché non esiste un'aritmetica di "riferimento", il compilatore deve solo dimostrare che forse Modify non ottiene mai l'indirizzo di x, e ha dimostrato che nulla cambia array [1].

Deve anche dimostrare che non ci sono modi in cui una chiamata futura potrebbe leggere / scrivere un [0] mentre abbiamo una copia temporanea del registro in a0. Questo è spesso banale da dimostrare, perché in molti casi è ovvio che il riferimento non è mai memorizzato in una struttura permanente come un'istanza di classe.

Ora fai la stessa cosa con i puntatori

void maybeModify(int* x); // May modify x in some way

void hurtTheCompilersOptimizer(short size, int array[])
{
    // Same operation, only now with pointers, making the
    // optimization trickier.
    for (int i = 2; i < (int)size; i++) {
        maybeModify(&(array[0]));
        array[i] += array[1];
    }
}

Il comportamento è lo stesso; solo ora è molto più difficile dimostrare che forse Modify non modifica mai l'array [1], perché gli abbiamo già dato un puntatore; il gatto è fuori dalla borsa. Ora deve fare la prova molto più difficile: un'analisi statica di forseModify per dimostrare che non scrive mai in & x + 1. Deve anche dimostrare che non salva mai un puntatore che può riferirsi all'array [0], che è solo così difficile.

I compilatori moderni stanno migliorando sempre di più nell'analisi statica, ma è sempre bello aiutarli e utilizzare i riferimenti.

Ovviamente, escludendo tali intelligenti ottimizzazioni, i compilatori trasformeranno effettivamente i riferimenti in puntatori quando necessario.




The difference is that non-constant pointer variable(not to be confused with a pointer to constant) may be changed at some time during program execution, requires pointer semantics to be used(&,*) operators, while references can be set upon initialization only(that's why you can set them in constructor initializer list only, but not somehow else) and use ordinary value accessing semantics. Basically references were introduced to allow support for operators overloading as I had read in some very old book. As somebody stated in this thread - pointer can be set to 0 or whatever value you want. 0(NULL, nullptr) means that the pointer is initialized with nothing. It is an error to dereference null pointer. But actually the pointer may contain a value that doesn't point to some correct memory location. References in their turn try not to allow a user to initialize a reference to something that cannot be referenced due to the fact that you always provide rvalue of correct type to it. Although there are a lot of ways to make reference variable be initialized to a wrong memory location - it is better for you not to dig this deep into details. On machine level both pointer and reference work uniformly - via pointers. Let's say in essential references are syntactic sugar. rvalue references are different to this - they are naturally stack/heap objects.




Another interesting use of references is to supply a default argument of a user-defined type:

class UDT
{
public:
   UDT() : val_d(33) {};
   UDT(int val) : val_d(val) {};
   virtual ~UDT() {};
private:
   int val_d;
};

class UDT_Derived : public UDT
{
public:
   UDT_Derived() : UDT() {};
   virtual ~UDT_Derived() {};
};

class Behavior
{
public:
   Behavior(
      const UDT &udt = UDT()
   )  {};
};

int main()
{
   Behavior b; // take default

   UDT u(88);
   Behavior c(u);

   UDT_Derived ud;
   Behavior d(ud);

   return 1;
}

The default flavor uses the 'bind const reference to a temporary' aspect of references.




I feel like there is yet another point that hasn't been covered here.

Unlike the pointers, references are syntactically equivalent to the object they refer to, ie any operation that can be applied to an object works for a reference, and with the exact same syntax (the exception is of course the initialization).

While this may appear superficial, I believe this property is crucial for a number of C++ features, for example:

  • Templates . Since template parameters are duck-typed, syntactic properties of a type is all that matters, so often the same template can be used with both T and T& .
    (or std::reference_wrapper<T> which still relies on an implicit cast to T& )
    Templates that cover both T& and T&& are even more common.

  • Lvalues . Consider the statement str[0] = 'X'; Without references it would only work for c-strings ( char* str ). Returning the character by reference allows user-defined classes to have the same notation.

  • Copy constructors . Syntactically it makes sense to pass objects to copy constructors, and not pointers to objects. But there is just no way for a copy constructor to take an object by value - it would result in a recursive call to the same copy constructor. This leaves references as the only option here.

  • Operator overloads . With references it is possible to introduce indirection to an operator call - say, operator+(const T& a, const T& b) while retaining the same infix notation. This also works for regular overloaded functions.

These points empower a considerable part of C++ and the standard library so this is quite a major property of references.




Related