3903 votes

Quelles sont les différences entre une variable de type pointeur et une variable de type référence ?

Quelle est la différence entre une variable de type pointeur et une variable de type référence ?

1 votes

Une référence locale (c'est-à-dire une référence qui ne se trouve pas dans une structure ou une classe) n'alloue pas nécessairement de l'espace de stockage. La différence entre sizeof(int &) et sizeof(struct { int &x ; }) permet de s'en rendre compte.

134 votes

Je pense que le point 2 devrait être "Un pointeur est autorisé à être NULL mais une référence ne l'est pas. Seul un code malformé peut créer une référence NULL et son comportement est indéfini."

31 votes

Les pointeurs sont un autre type d'objet et, comme tout objet en C++, ils peuvent être une variable. Les références, en revanche, ne sont jamais des objets, seulement variables.

2206voto

Brian R. Bondy Points 141769
  1. Un pointeur peut être réaffecté :

    int x = 5;
    int y = 6;
    int *p;
    p = &x;
    p = &y;
    *p = 10;
    assert(x == 5);
    assert(y == 10);

    Une référence ne peut pas être reliée à nouveau et doit être reliée lors de l'initialisation :

    int x = 5;
    int y = 6;
    int &q; // error
    int &r = x;
  2. Une variable de type pointeur possède sa propre identité : une adresse mémoire distincte et visible qui peut être prise avec la fonction unaire & et une certaine quantité d'espace qui peut être mesurée à l'aide de l'instrument de mesure. sizeof de l'opérateur. L'utilisation de ces opérateurs sur une référence renvoie une valeur correspondant à la variable à laquelle la référence est liée ; l'adresse et la taille de la référence sont invisibles. Étant donné que la référence assume l'identité de la variable originale de cette manière, il est pratique de considérer une référence comme un autre nom pour la même variable.

    int x = 0;
    int &r = x;
    int *p = &x;
    int *p2 = &r;
    
    assert(p == p2); // &x == &r
    assert(&p != &p2);
  3. Il est possible d'imbriquer arbitrairement les pointeurs dans les pointeurs, ce qui offre des niveaux d'indirection supplémentaires. Les références n'offrent qu'un seul niveau d'indirection.

    int x = 0;
    int y = 0;
    int *p = &x;
    int *q = &y;
    int **pp = &p;
    
    **pp = 2;
    pp = &q; // *pp is now q
    **pp = 4;
    
    assert(y == 4);
    assert(x == 2);
  4. Un pointeur peut être attribué nullptr alors qu'une référence doit être liée à un objet existant. Si vous vous efforcez de le faire, vous pouvez lier une référence à nullptr mais il s'agit d'une indéfini et ne se comporteront pas de manière cohérente.

    /* the code below is undefined; your compiler may optimise it
     * differently, emit warnings, or outright refuse to compile it */
    
    int &r = *static_cast<int *>(nullptr);
    
    // prints "null" under GCC 10
    std::cout
        << (&r != nullptr
            ? "not null" : "null")
        << std::endl;
    
    bool f(int &r) { return &r != nullptr; }
    
    // prints "not null" under GCC 10
    std::cout
        << (f(*static_cast<int *>(nullptr))
            ? "not null" : "null")
        << std::endl;

    Vous pouvez cependant avoir une référence à un pointeur dont la valeur est nullptr .

  5. Les pointeurs peuvent itérer sur un tableau ; vous pouvez utiliser ++ pour passer à l'élément suivant sur lequel pointe un pointeur, et + 4 pour passer au 5ème élément. Et ce, quelle que soit la taille de l'objet sur lequel pointe le pointeur.

  6. Un pointeur doit être déréférencé à l'aide de la fonction * pour accéder à l'emplacement mémoire qu'il pointe, alors qu'une référence peut être utilisée directement. Un pointeur sur une classe/struct utilise -> pour accéder à ses membres, alors qu'une référence utilise un . .

  7. Les références ne peuvent pas être placées dans un tableau, alors que les pointeurs peuvent l'être (Mentionné par l'utilisateur @litb)

  8. Les références Const peuvent être liées à des temporaires. Les pointeurs ne le peuvent pas (pas sans une certaine indirection) :

    const int &x = int(12); // legal C++
    int *y = &int(12); // illegal to take the address of a temporary.

    Ce qui fait que const & plus pratique à utiliser dans les listes d'arguments, etc.

2 votes

La référence peut être nulle. Dans certains cas, vous passez les paramètres de la référence comme ceci : function(*ptr) ; Si ptr est NULL, la référence le sera aussi.

30 votes

...mais le déréférencement de NULL n'est pas défini. Par exemple, vous ne pouvez pas tester si une référence est NULL (par exemple, &ref == NULL).

1 votes

En VC++, *ptr provoque un crash, et il est presque certain qu'en Gc++, il y a aussi un défaut de segmentation.

542voto

Christoph Points 64389

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

A référence peut être considéré comme un pointeur constant (à ne pas confondre avec un pointeur sur une valeur constante !) avec une indirection automatique, c'est-à-dire que le compilateur appliquera le code * pour vous.

Toutes les références doivent être initialisées avec une valeur non nulle, sinon la compilation échouera. Il n'est pas possible d'obtenir l'adresse d'une référence - l'opérateur d'adresse renvoie l'adresse de la valeur référencée - ni d'effectuer des opérations arithmétiques sur les références.

Les programmeurs C pourraient ne pas aimer les références C++, car il ne sera plus évident de savoir s'il y a indirection ou si un argument est transmis par une valeur ou par un pointeur sans regarder les signatures de fonction.

Les programmeurs C++ peuvent ne pas aimer utiliser les pointeurs car ils sont considérés comme peu sûrs - bien que les références ne soient pas vraiment plus sûres que les pointeurs constants, sauf dans les cas les plus triviaux -, ils n'ont pas la commodité de l'indirection automatique et ont une connotation sémantique différente.

Examinez la déclaration suivante tirée de la FAQ C++ :

Même si une référence est souvent mise en œuvre langage d'assemblage sous-jacent, veuillez ne pas pas penser à une référence un pointeur à l'aspect étrange vers un objet. Une référence est l'objet. Il ne s'agit pas d'un pas un pointeur sur l'objet, ni une copie de l'objet. Il n'est pas non plus une copie de l'objet. est t l'objet.

Mais si une référence vraiment était 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 aucun moyen d'aliaser des valeurs de manière fiable au-delà des limites du champ d'application !

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

Venant d'un milieu C, les références C++ peuvent sembler un concept quelque peu stupide, mais on devrait quand même les utiliser à la place des pointeurs lorsque c'est possible : indirection automatique est pratique, et les références deviennent particulièrement utiles lorsqu'il s'agit de RAII - mais pas en raison d'un avantage perçu en termes de sécurité, mais plutôt parce qu'ils rendent l'écriture de code idiomatique moins gênante.

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

1 votes

@Christoph : les références ne peuvent être pendantes que si vous les avez obtenues quelque part en déréférençant un pointeur.

22 votes

@kriss : Non, vous pouvez également obtenir une référence pendante en renvoyant une variable automatique par référence.

2 votes

Ben Voight : D'accord, mais c'est vraiment un appel aux problèmes. Certains compilateurs renvoient même des avertissements si vous faites cela.

228voto

Matt Price Points 9674

Si vous voulez être vraiment pédant, il y a une chose que vous pouvez faire avec une référence et que vous ne pouvez pas faire avec un pointeur : prolonger la durée de vie d'un objet temporaire. En C++, si vous liez une référence constante à un objet temporaire, la durée de vie de cet objet devient la durée de vie de la référence.

std::string s1 = "123";
std::string s2 = "456";

std::string s3_copy = s1 + s2;
const std::string& s3_reference = s1 + s2;

Dans cet exemple, s3_copy copie l'objet temporaire résultant de la concaténation. Alors que s3_reference devient essentiellement l'objet temporaire. Il s'agit en fait d'une référence à un objet temporaire qui a maintenant la même durée de vie que la référence.

Si vous essayez de le faire sans le const la compilation devrait échouer. Vous ne pouvez pas lier une référence non-const à un objet temporaire, pas plus que vous ne pouvez prendre son adresse d'ailleurs.

7 votes

Mais quel est le cas d'utilisation ?

26 votes

Eh bien, s3_copy crée un temporaire et le construit ensuite par copie dans s3_copy, alors que s3_reference utilise directement le temporaire. Ensuite, pour être vraiment pédant, il faut regarder l'optimisation de la valeur de retour, qui permet au compilateur d'éluder la construction de la copie dans le premier cas.

7 votes

@digitalSurgeon : La magie est assez puissante. La durée de vie de l'objet est prolongée par le fait que le const & et ce n'est que lorsque la référence sort du champ d'application que le destructeur de l'élément réel Le type référencé (par rapport au type de référence, qui peut être une base) est appelé. Comme il s'agit d'une référence, aucun découpage n'aura lieu entre les deux.

139voto

Mark Ransom Points 132545

Contrairement à ce que l'on pense, il est possible d'avoir une référence NULLE.

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 vous arracherez les cheveux pour la trouver. Les références sont pas intrinsèquement sûr en C++ !

Techniquement, il s'agit d'une référence non valide et non une référence nulle. Le C++ ne prend pas en charge les références nulles en tant que concept, comme c'est le cas dans d'autres langages. Il existe également d'autres types de références non valides. Tous La référence non valide soulève le spectre d'une comportement non défini comme le ferait l'utilisation d'un pointeur non valide.

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

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 à rappeler que le seul moyen d'obtenir une référence nulle est d'utiliser un code malformé, et qu'une fois que vous l'avez, vous obtenez un comportement indéfini. Il nunca il est logique de vérifier si la référence est nulle ; par exemple, vous pouvez essayer if(&bar==NULL)... mais le compilateur risque d'optimiser la déclaration pour qu'elle n'existe plus ! Une référence valide ne peut jamais être NULL, donc du point de vue du compilateur, la comparaison est toujours fausse, et il est libre d'éliminer l'élément if comme du code mort - c'est l'essence même d'un comportement non défini.

Pour éviter les problèmes, il faut éviter de déréférencer un pointeur NULL pour créer une référence. Voici une méthode automatisée pour y parvenir.

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 examen plus ancien de ce problème par quelqu'un qui a de meilleures compétences en matière d'écriture, voir Références nulles de Jim Hyslop et Herb Sutter.

Pour un autre exemple des dangers du déréférencement d'un pointeur nul, voir Exposition d'un comportement indéfini lors du portage du code sur une autre plate-forme par Raymond Chen.

4 votes

Je dirais que la référence n'est pas réellement nulle, mais qu'elle fait référence à une mauvaise mémoire. Bien que le fait qu'il s'agisse d'une référence ne signifie pas qu'elle se réfère à quelque chose de valable.

71 votes

Le code en question contient un comportement indéfini. Techniquement, vous ne pouvez rien faire avec un pointeur nul, sauf le définir et le comparer. Une fois que votre programme invoque un comportement non défini, il peut faire n'importe quoi, y compris sembler fonctionner correctement jusqu'à ce que vous fassiez une démonstration au grand patron.

1 votes

Je ne l'ai pas testé mais je pense que le code ci-dessus se plante à la deuxième ligne lors du déréférencement du pointeur NULL.

131voto

Orion Edwards Points 54939

Vous avez oublié le plus important :

L'accès aux membres avec des pointeurs utilise ->
accès des membres avec références utilisations .

foo.bar est clairement supérieur à foo->bar de la même manière que vi est clairement supérieur à Emacs :-)

6 votes

@Orion Edwards >l'accès aux membres avec pointeurs utilise -> >l'accès aux membres avec références utilise . Ce n'est pas vrai à 100 %. Il est possible d'avoir une référence à un pointeur. Dans ce cas, vous accédez aux membres du pointeur déréférencé en utilisant -> struct Node { Node *next ; } ; Node *first ; // p est une référence à un pointeur void foo(Node*&p) { p->next = first ; } Node *bar = new Node ; foo(bar) ; -- OP : Êtes-vous familier avec les concepts de rvalues et lvalues ?

4 votes

Les pointeurs intelligents ont à la fois . (méthodes sur la classe du pointeur intelligent) et -> (méthodes sur le type sous-jacent).

2 votes

@user6105 Orion Edwards est en fait 100% vrai. "accéder aux membres du pointeur déréférencé" Un pointeur n'a pas de membres. L'objet auquel le pointeur fait référence possède des membres, et l'accès à ces derniers est exactement ce que fait le -> fournit des références aux pointeurs, tout comme le pointeur lui-même.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X