Pourquoi n’ai-je pas besoin de vérifier si les références sont invalides / nulles?

En lisant http://www.cprogramming.com/tutorial/references.html , il est écrit:

En général, les références doivent toujours être valides, car vous devez toujours initialiser une référence. Cela signifie que sauf quelques circonstances bizarres (voir ci-dessous), vous pouvez être certain que l’utilisation d’une référence équivaut à l’utilisation d’une ancienne variable non référencée. Vous n’avez pas besoin de vérifier si une référence ne pointe pas vers NULL et vous ne serez pas piqué par une référence non initialisée pour laquelle vous avez oublié d’allouer de la mémoire.

Ma question est la suivante: comment savoir si la mémoire de l’object n’a pas été libérée / supprimée APRÈS que vous ayez initialisé la référence.

En résumé, je ne peux pas suivre ce conseil avec foi et j’ai besoin d’une meilleure explication.

Quelqu’un peut-il nous éclairer?

Vous ne pouvez pas savoir si les références ne sont pas valides:

Il n’y a aucun moyen de savoir si votre référence fait référence à une mémoire valide, sauf en prenant soin de votre utilisation des références. Par exemple, vous ne souhaitez pas utiliser une référence avec quelque chose créé sur le tas si vous ne savez pas quand la mémoire sera supprimée.

En outre, vous ne pouvez jamais savoir si le pointeur que vous utilisez pointe vers une mémoire valide ou non.

Vous pouvez effectuer des vérifications NULL avec des pointeurs et des références, mais en règle générale, vous ne feriez jamais une vérification NULL avec une référence car personne n’écrirait un code comme celui-ci:

 int *p = 0; int &r = *p;//no one does this if(&r != 0)//and so no one does this kind of check { } 

Quand utiliser une référence?

Vous voudrez probablement utiliser des références dans des cas comme celui-ci:

 //I want the function fn to not make a copy of cat and to use // the same memory of the object that was passed in void fn(Cat &cat) { //Do something with cat } //...main... Cat c; fn(c); 

Se tirer dans le pied est difficile avec des références:

Il est beaucoup plus difficile de se tirer dans le pied avec des références qu’avec des pointeurs.

Par exemple:

 int *p; if(true) { int x; p = &x; } *p = 3;//runtime error 

Vous ne pouvez pas faire ce genre de chose avec des références car une référence doit être initialisée avec sa valeur. Et vous ne pouvez l’initialiser qu’avec des valeurs qui sont dans votre scope.

Vous pouvez toujours vous tirer dans le pied avec des références, mais vous devez VRAIMENT essayer de le faire.

Par exemple:

 int *p = new int; *p = 3; int &r = *p; delete p; r = 3;//runtime error 

Tu ne peux pas. Vous ne pouvez pas non plus avec un pointeur. Considérer:

 struct X { int * i; void foo() { *i++; } }; int main() { int *i = new int(5); X x = { i }; delete i; x.foo(); } 

Maintenant, quel code pourriez-vous mettre dans X :: foo () pour vous assurer que le pointeur i est toujours valide?

La réponse est qu’il n’y a pas de contrôle standard. Il existe certaines astuces qui pourraient fonctionner sur msvc en mode débogage (vérification de 0xfeeefeee ou autre), mais rien ne fonctionnera de manière cohérente.

Si vous avez besoin d’une sorte d’object assurant que le pointeur ne pointe pas sur la mémoire libérée, vous aurez besoin de quelque chose de beaucoup plus intelligent qu’une référence ou d’un pointeur standard.

C’est pourquoi vous devez faire très attention à la sémantique de la propriété et à la gestion de la durée de vie lorsque vous travaillez avec des pointeurs et des références.

Ma question est la suivante: comment savoir si la mémoire de l’object n’a pas été libérée / supprimée APRÈS que vous ayez initialisé la référence.

Tout d’abord, il n’y a jamais aucun moyen de détecter si un emplacement de mémoire a été libéré / supprimé. Cela n’a rien à voir avec le caractère nul ou non. La même chose est vraie pour un pointeur. Avec un pointeur, vous n’avez aucun moyen de vous assurer qu’il pointe vers une mémoire valide. Vous pouvez tester si un pointeur est nul ou non, mais c’est tout. Un pointeur non nul peut toujours pointer sur de la mémoire libérée. Ou cela peut pointer vers un emplacement de déchets.

En ce qui concerne les références, il en va de même en ce que vous ne pouvez pas déterminer si elles font référence à un object toujours valide. Cependant, il n’y a pas de “référence nulle” en C ++, il n’est donc pas nécessaire de vérifier si une référence “est nulle”.

Bien sûr, il est possible d’écrire du code qui crée ce qui ressemble à une “référence nulle”, et ce code sera compilé. Mais ce ne sera pas correct . Selon le standard de langage C ++, les références à null ne peuvent pas être créées. Tenter de le faire est un comportement indéfini.

En résumé, je ne peux pas suivre ce conseil avec foi et j’ai besoin d’une meilleure explication.

La meilleure explication est la suivante: “une référence pointe sur un object valide car vous la définissez pour pointer sur un object valide”. Vous n’êtes pas obligé de le croire. Il suffit de regarder le code où vous avez créé la référence. Cela indiquait un object valide à ce moment ou non. Si ce n’est pas le cas, le code est incorrect et doit être modifié.

Et la référence est toujours valide car vous savez qu’elle va être utilisée. Vous vous êtes donc assuré de ne pas invalider l’object auquel il fait référence.

C’est vraiment aussi simple que cela. Les références restnt valables tant que vous ne détruisez pas l’object qu’elles désignent. Donc, ne détruisez pas l’object sur lequel il pointe jusqu’à ce que la référence ne soit plus nécessaire.

En C ++, les références sont principalement destinées à être utilisées en tant que parameters et types de fonctions retournés. Dans le cas d’un paramètre, une référence ne peut pas faire référence à un object qui n’existe plus (en supposant un seul programme threadé) en raison de la nature d’un appel de fonction. Dans le cas d’une valeur de retour, il convient de se limiter soit à renvoyer des variables de membre de classe dont la durée de vie est supérieure à celle de l’appel de la fonction, soit à des parameters de référence transmis à la fonction.

Vous devez préserver la santé de vos variables – autrement dit, ne passez une référence / un pointeur à une fonction que si vous savez que la scope de la fonction ne survivra pas à votre référence / pointeur.

Si vous libérez une poignée puis essayez d’utiliser cette référence, vous lirez de la mémoire en lecture libre.

Car au moment où vous y arrivez, vous avez certainement adopté un comportement indéfini. Laisse-moi expliquer 🙂

Disons que vous avez:

 void fun(int& n); 

Maintenant, si vous passez quelque chose comme:

 int* n = new int(5); fun(*n); // no problems until now! 

Mais si vous procédez comme suit:

 int* n = new int(5); ... delete n; ... fun(*n); // passing a deleted memory! 

Au moment où vous atteignez l’ fun , vous déréférencerez * n, ce qui est un comportement indéfini si le pointeur est supprimé comme dans l’exemple ci-dessus. Donc, il n’y a pas moyen, et il doit y en avoir maintenant parce qu’assumer des parameters valables est tout le sharepoint référence.

Il n’y a pas de syntaxe pour vérifier si la référence est valide. Vous pouvez tester le pointeur pour la valeur NULL, mais il n’y a pas de test valide / non valide pour une référence. Bien sûr, les objects référencés peuvent être libérés ou remplacés par un code erroné. La même situation est pour les pointeurs: si le pointeur non-NULL pointe sur un object libéré, vous ne pouvez pas le tester.

En bref, cela peut arriver, mais si cela se produit, vous rencontrez un grave problème de conception. Vous n’avez également aucun moyen de le détecter. La solution consiste à concevoir votre programme de manière à éviter que cela ne se produise, et non à essayer de créer une sorte de vérification qui ne fonctionnera pas vraiment (car elle ne peut pas).

Les références C ++ sont des alias. L’effet de ceci est que les déréférences aux pointeurs ne se produisent pas nécessairement là où elles apparaissent, elles se produisent là où elles sont évaluées. Prendre une référence à un object n’évalue pas l’object, il l’alias. Utiliser la référence est ce qui évalue l’object. C ++ ne peut pas garantir que les références sont valides. si c’est le cas, tous les compilateurs C ++ sont cassés. Le seul moyen de le faire est d’éliminer toute possibilité d’allocation dynamic avec références. En pratique, l’hypothèse est qu’une référence est un object valide. Puisque * NULL est indéfini et invalide, il en résulte que pour p = NULL, * p est également indéfini. Le problème avec C ++ est que * p sera effectivement passé à une fonction ou retardé dans son évaluation jusqu’à quelle heure la référence est réellement utilisée. Faire valoir que cela n’est pas défini n’est pas le but de la question de l’interrogateur. Si c’était illégal, le compilateur l’appliquerait, tout comme la norme. Je ne le sais pas non plus.

 int &r = *j; // aliases *j, but does not evaluate j if(r > 0) // evaluates r ==> *j, resulting in dereference (evaluate j) at this line, not what some expect ; 

1) Vous pouvez tester une référence pour aliaser un pointeur NULL, & r est simplement & (quel que soit l’alias r) (EDIT)

2) Lors du passage d’un pointeur “déréférencé” (* i) en tant que paramètre de référence, le déréférencement ne se produit pas sur le site d’appels, cela peut ne jamais se produire, car il s’agit d’une référence (les références sont des alias, pas des évaluations). C’est l’optimisation des références. S’ils ont été évalués sur le site d’appels, le compilateur est en train d’insérer du code supplémentaire ou ce serait un appel par valeur moins performant qu’un pointeur.

Oui, la référence elle-même n’est pas NULL, elle n’est pas valide, tout comme * NULL est invalide. C’est le fait de retarder l’évaluation des expressions de déréférencement qui ne concorde pas avec l’affirmation selon laquelle il est impossible d’avoir une référence invalide.

 #include  int fun(int & i) { std::cerr << &i << "\n"; std::cerr << i << "\n"; // crash here } int main() { int * i = new int(); i = 0; fun(*i); // Why not crash here? Because the deref doesn't happen here, inconsistent, but critical for performance of references } 

EDIT: Modifié mon exemple car il a été interprété à tort comme une suggestion de test de références, et non comme ce que je voulais démontrer. Je voulais seulement démontrer la référence invalide.

Je pense que cela “dépend”. Je sais que ce n’est pas une réponse, mais cela dépend vraiment. Je pense que coder de manière défensive est une bonne pratique à suivre. Désormais, si votre piste de stack a une profondeur de 10 niveaux et que tout échec de la trace entraîne l’échec de l’opération entière, il est important de vérifier au premier niveau et de laisser toutes les exceptions se propager au maximum. mais si vous pouvez récupérer de quelqu’un en vous passant une référence nulle, cochez la case appropriée. D’après mon expérience, où je dois rassembler du code avec d’autres entresockets pour l’intégrer, la vérification (et la journalisation) de tout au niveau de l’API publique vous permet de détourner le doigt qui se produit lorsque l’intégration ne se déroule pas comme prévu.

Je pense que vous pourriez bénéficier d’un simple parallélisme:

  • T & est similaire à T * const
  • T const & est similaire à T const * const

Les références sont très similaires à const dans leur intention, elles ont une signification et aident donc à écrire du code plus clair, mais ne fournissent pas un comportement différent lors de l’exécution.

Maintenant, pour répondre à votre question: oui, il est possible qu’une référence soit nulle ou invalide. Vous pouvez tester une référence nulle ( T& t = ; if (&t == 0) ), mais cela ne devrait pas arriver >> par contrat, une référence est valide.

Quand utiliser référence vs pointeur? Utilisez un pointeur si:

  • vous souhaitez pouvoir changer de pointe
  • vous souhaitez exprimer la nullité éventuelle

Dans les autres cas, utilisez une référence.

Quelques exemples:

 // Returns an object corresponding to the criteria // or a special value if it cannot be found Object* find(...); // returns 0 if fails // Returns an object corresponding to the criteria // or throw "NotFound" if it cannot be found Object& find(...); // throw NotFound 

Arguments de passage:

 void doSomething(Object* obj) { if (obj) obj->doSomething(); } void doSomething(Object& obj) { obj.do(); obj.something(); } 

Les atsortingbuts:

 struct Foo { int i; Bar* b; // No constructor, we need to initialize later on }; class Foo { public: Foo(int i, Bar& b): i(i), b(b) {} private: int i; Bar& b; // will always point to the same object, Foo not Default Constructible }; class Other { public: Other(Bar& b): b(&b) {} // NEED to pass a valid object for init void swap(Other& rhs); // NEED a pointer to be able to exchange private: Bar* b; }; 

Les références fonctionnelles et les pointeurs jouent exactement le même rôle. C’est juste une question de contrat. Et malheureusement, les deux peuvent invoquer le comportement indéfini si vous supprimez l’object auquel ils font référence, il n’y a pas de gagnant ici;)