[c++] Quelles sont les différences entre une variable pointeur et une variable de référence en C ++?



14 Answers

Qu'est-ce qu'une référence C ++ ( pour les programmeurs C )

Une référence peut être considérée comme un pointeur constant (à ne pas confondre avec un pointeur vers une valeur constante!) Avec une indirection automatique, c'est-à-dire que le compilateur appliquera l'opérateur * pour vous.

Toutes les références doivent être initialisées avec une valeur non nulle ou la compilation échouera. Il n'est pas possible d'obtenir l'adresse d'une référence - l'opérateur d'adresse retournera à la place l'adresse de la valeur référencée - il n'est pas non plus possible de faire de l'arithmétique sur les références.

Les programmeurs C peuvent ne pas aimer les références C ++ car elles ne seront plus évidentes lorsqu'une indirection se produit ou si un argument est passé par valeur ou par pointeur sans regarder les signatures de fonction.

Les programmeurs C ++ peuvent ne pas aimer utiliser des pointeurs car ils sont considérés comme dangereux - bien que les références ne soient pas plus sûres que les pointeurs constants sauf dans les cas les plus triviaux - manquent de commodité d'indirection automatique et portent une connotation sémantique différente.

Considérez l'instruction suivante de la FAQ C ++ :

Même si une référence est souvent implémentée à l'aide d'une adresse dans le langage assembleur sous-jacent, ne pensez pas à une référence comme un pointeur d'un objet. Une référence est l'objet. Ce n'est pas un pointeur sur l'objet, ni une copie de l'objet. C'est l'objet.

Mais si une référence était vraiment l'objet, comment pourrait-il y avoir des références pendantes? Dans les langages non gérés, il est impossible que les références soient plus «sûres» que les pointeurs - il n'y a généralement pas moyen d'alias de manière fiable sur les limites de la portée!

Pourquoi je considère que les références C ++ sont utiles

Venant d'un arrière-plan C, les références C ++ peuvent sembler un peu stupides, mais on devrait toujours les utiliser à la place des pointeurs si possible: L'indirection automatique est pratique, et les références deviennent particulièrement utiles avec RAII . avantage, mais plutôt parce qu'ils rendent l'écriture de code idiomatique moins gênante.

RAII est l'un des concepts centraux de C ++, mais il interagit de manière non triviale avec la sémantique de copie. Passer des objets par référence permet d'éviter ces problèmes car il n'y a pas de copie. Si les références n'étaient pas présentes dans la langue, vous devriez utiliser des pointeurs, qui sont plus encombrants à utiliser, violant ainsi le principe de conception du langage selon lequel la solution des meilleures pratiques devrait être plus facile que les alternatives.

Question

Je sais que les références sont du sucre syntaxique, donc le code est plus facile à lire et à écrire.

Mais quelles sont les différences?

Résumé des réponses et des liens ci-dessous:

  1. Un pointeur peut être réattribué n'importe quel nombre de fois tandis qu'une référence ne peut pas être réassignée après la liaison.
  2. Les pointeurs ne peuvent pointer nulle part ( NULL ), alors que la référence fait toujours référence à un objet.
  3. Vous ne pouvez pas prendre l'adresse d'une référence comme vous pouvez avec des pointeurs.
  4. Il n'y a pas de "référence arithmétique" (mais vous pouvez prendre l'adresse d'un objet pointé par une référence et faire un arithmétique de pointeur dessus comme dans &obj + 5 ).

Pour clarifier une idée fausse:

La norme C ++ est très prudente pour éviter de dicter comment un compilateur doit implémenter des références, mais chaque compilateur C ++ implémente des références en tant que pointeurs. C'est-à-dire une déclaration telle que:

int &ri = i;

si elle n'est pas entièrement optimisée , alloue la même quantité de stockage qu'un pointeur et place l'adresse de i dans ce stockage.

Ainsi, un pointeur et une référence occupent tous les deux la même quantité de mémoire.

En règle générale,

  • Utilisez des références dans les paramètres de fonction et les types de retour pour définir des interfaces utiles et auto-documentées.
  • Utilisez des pointeurs pour implémenter des algorithmes et des structures de données.

Lecture intéressante:




En dehors du sucre syntaxique, une référence est un pointeur const ( pas un pointeur vers un const ). Vous devez définir à quoi il se réfère lorsque vous déclarez la variable de référence et vous ne pouvez pas le modifier ultérieurement.

Mise à jour: maintenant que j'y pense un peu plus, il y a une différence importante.

La cible d'un pointeur de const peut être remplacée en prenant son adresse et en utilisant une distribution constante.

La cible d'une référence ne peut être remplacée en aucune façon en dessous de UB.

Cela devrait permettre au compilateur de faire plus d'optimisation sur une référence.




Les références sont très similaires aux pointeurs, mais elles sont spécialement conçues pour faciliter l'optimisation des compilateurs.

  • Les références sont conçues de telle sorte qu'il est beaucoup plus facile pour le compilateur de trouver quels alias de référence quelles variables. Deux caractéristiques majeures sont très importantes: pas de "référence arithmétique" et pas de réaffectation des références. Ceux-ci permettent au compilateur de déterminer quelles références alias quelles variables à la compilation.
  • Les références sont autorisées à se référer à des variables qui n'ont pas d'adresses mémoire, comme celles que le compilateur choisit de placer dans des registres. Si vous prenez l'adresse d'une variable locale, il est très difficile pour le compilateur de la mettre dans un registre.

Par exemple:

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 compilateur qui optimise peut réaliser que nous accédons à un [0] et un [1] tout à fait un tas. Il aimerait optimiser l'algorithme pour:

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
}

Pour faire une telle optimisation, il faut prouver que rien ne peut changer array [1] pendant l'appel. C'est plutôt facile à faire. i n'est jamais inférieur à 2, donc array [i] ne peut jamais se référer à array [1]. maybeModify () reçoit a0 comme référence (aliasing array [0]). Parce qu'il n'y a pas d'arithmétique "de référence", le compilateur doit juste prouver que maybeModify n'obtient jamais l'adresse de x, et il a prouvé que rien ne change array [1].

Il doit aussi prouver qu'il n'y a aucun moyen qu'un futur appel puisse lire / écrire un [0] alors que nous en avons une copie temporaire dans a0. C'est souvent trivial à prouver, car dans de nombreux cas, il est évident que la référence n'est jamais stockée dans une structure permanente comme une instance de classe.

Maintenant, faites la même chose avec des pointeurs

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];
    }
}

Le comportement est le même; seulement maintenant il est beaucoup plus difficile de prouver que maybeModify ne modifie jamais array [1], parce que nous lui avons déjà donné un pointeur; le chat est sorti du sac. Maintenant, il doit faire la preuve beaucoup plus difficile: une analyse statique de maybeModify pour prouver qu'il n'écrit jamais à & x + 1. Il doit également prouver qu'il ne sauvegarde jamais un pointeur qui peut se référer à array [0], qui est juste aussi difficile.

Les compilateurs modernes deviennent de mieux en mieux à l'analyse statique, mais il est toujours agréable de les aider et d'utiliser des références.

Bien sûr, à moins de telles optimisations intelligentes, les compilateurs transformeront effectivement les références en pointeurs si nécessaire.




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.




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.




Alors que les références et les pointeurs sont utilisés pour accéder indirectement à une autre valeur, il existe deux différences importantes entre les références et les pointeurs. La première est qu'une référence fait toujours référence à un objet: c'est une erreur de définir une référence sans l'initialiser. Le comportement de l'assignation est la deuxième différence importante: Assigner à une référence change l'objet auquel la référence est liée; il ne renvoie pas la référence à un autre objet. Une fois initialisée, une référence fait toujours référence au même objet sous-jacent.

Considérez ces deux fragments de programme. Dans le premier, nous assignons un pointeur à un autre:

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

Après l'affectation, ival, l'objet adressé par pi reste inchangé. L'affectation modifie la valeur de pi, en la faisant pointer vers un objet différent. 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.




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. 



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.

Par exemple:

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

...

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

va imprimer:

in scope
scope_test done!

This is the language mechanism that allows ScopeGuard to work.




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.




Contrairement à l'opinion populaire, il est possible d'avoir une référence qui est NULL.

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

Certes, c'est beaucoup plus difficile à faire avec une référence - mais si vous y parvenez, vous allez vous arracher les cheveux en essayant de le trouver. Les références ne sont pas intrinsèquement sûres en C ++!

Techniquement, c'est une référence invalide , pas une référence nulle. C ++ ne supporte pas les références nulles en tant que concept comme vous pourriez le trouver dans d'autres langages. Il existe également d'autres types de références invalides. Toute référence invalide soulève le spectre d' un comportement indéfini , tout comme l'utilisation d'un pointeur invalide.

L'erreur réelle est dans le déréférencement du pointeur NULL, avant l'affectation à une référence. Mais je ne suis au courant d'aucun compilateur qui générera des erreurs sur cette condition - l'erreur se propage à un point plus loin dans le code. C'est ce qui rend ce problème si insidieux. La plupart du temps, si vous déréférencer un pointeur NULL, vous plantez directement à cet endroit et il ne faudra pas beaucoup de débogage pour le comprendre.

Mon exemple ci-dessus est court et artificiel. Voici un exemple plus concret.

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);

Je tiens à réitérer que la seule façon d'obtenir une référence nulle est le code mal formé, et une fois que vous l'avez, vous obtenez un comportement indéfini. Il n'a jamais de sens de vérifier une référence nulle; par exemple, vous pouvez essayer if(&bar==NULL)... mais le compilateur pourrait optimiser l'instruction hors de l'existence! Une référence valide ne peut jamais être NULL, donc à partir de la vue du compilateur, la comparaison est toujours fausse, et il est libre d'éliminer la clause if comme code mort - c'est l'essence du comportement indéfini.

La meilleure façon d'éviter les problèmes est d'éviter de déréférencer un pointeur NULL pour créer une référence. Voici une manière automatisée d'accomplir ceci.

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));

Pour un vieux regard sur ce problème de quelqu'un avec de meilleures compétences en écriture, voir Null Références de Jim Hyslop et Herb Sutter.

Pour un autre exemple des dangers de déréférencement d'un pointeur nul, voir Exposition d'un comportement non défini lors d'une tentative de portage de code vers une autre plateforme par Raymond Chen.




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.




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) Une fois qu'une référence est créée, elle ne peut plus être faite pour référencer un autre objet; il ne peut pas être remis en place. Ceci est souvent fait avec des pointeurs.

2) Les références ne peuvent pas être NULL. Les pointeurs sont souvent NULL pour indiquer qu'ils ne pointent sur aucune chose valide.

3) Une référence doit être initialisée lorsqu'elle est déclarée. Il n'y a pas de telle restriction avec des pointeurs

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.

Les références sont plus sûres et plus faciles à utiliser:

1) Plus sûr: Puisque les références doivent être initialisées, les références sauvages comme les pointeurs sauvages ont peu de chance d'exister. Il est toujours possible d'avoir des références qui ne se réfèrent pas à un emplacement valide

2) Plus facile à utiliser: Les références n'ont pas besoin de déréférencer l'opérateur pour accéder à la valeur. Ils peuvent être utilisés comme des variables normales. L'opérateur '&' est nécessaire uniquement au moment de la déclaration. De plus, les membres d'une référence d'objet peuvent être accédés avec l'opérateur point ('.'), Contrairement aux pointeurs où l'opérateur de flèche (->) est nécessaire pour accéder aux membres.

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 ++ .




J'ai une analogie pour les références et les pointeurs, je pense aux références comme un autre nom pour un objet et aux pointeurs comme l'adresse d'un objet.

// 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
}



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




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;
}



Related