IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

FAQ C++Consultez toutes les FAQ

Nombre d'auteurs : 34, nombre de questions : 368, dernière mise à jour : 14 novembre 2021  Ajouter une question

 

Cette FAQ a été réalisée à partir des questions fréquemment posées sur les forums de http://www.developpez.com et de l'expérience personnelle des auteurs.

Je tiens à souligner que cette FAQ ne garantit en aucun cas que les informations qu'elle propose sont correctes ; les auteurs font le maximum, mais l'erreur est humaine. Cette FAQ ne prétend pas non plus être complète. Si vous trouvez une erreur ou si vous souhaitez devenir rédacteur, lisez ceci.

Sur ce, nous vous souhaitons une bonne lecture.

SommaireLes références (6)
précédent sommaire suivant
 

Une référence est un alias, un nom alternatif pour un objet. Le principe à retenir est que tout se passe comme si c'était l'objet lui-même et non une référence sur lui.

Les références sont souvent utilisées lors du passage de paramètres, en particulier avec le mot-clé const (référence constante : donc le paramètre passé est non modifiable) afin de rendre l'appel à la fonction plus performant sur des objets volumineux. Sans le mot-clé const l'usage d'une référence indique alors que le paramètre est modifié par la fonction.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// fonction qui échange i et j 
void swap(int& i, int& j) 
{ 
    int tmp = i; 
    i = j; 
    j = tmp; 
} 
  
int main() 
{ 
    int x = 1; 
    int y = 2; 
    swap( x, y ); 
    // x = 2, y = 1 
}
Dans cet exemple, i et j sont des alias pour x et y du main. En d'autres mots, i est x (pas un pointeur sur x, ni une copie, mais x lui-même). Tout ce qui est fait à x est fait à i et inversement.

Bon, maintenant, pensons aux références du point de vue du programmeur. Au risque de provoquer la confusion en donnant une autre perspective voici comment les références sont implémentées en pratique. Au fond, une référence i vers un objet x est habituellement son adresse. Mais quand le programmeur fait un i++, le compilateur génère du code qui incrémente x. Typiquement, les bits d'adressage que le compilateur utilise pour accéder à x sont inchangés. Un programmeur C pensera qu'il s'agit du passage d'un pointeur, avec les variantes syntaxiques suivantes :
  • Déplacer le & de l'appelant à l'appelé ;
  • Supprimer les notations *s.

En d'autres mots, un programmeur le considérera comme une macro pour (*p), où p est un pointeur sur x (par ex., le compilateur déréférencerait automatiquement le pointeur : i++ serait transformé en (*p)++).

Note : même si une référence est souvent implémentée en utilisant une adresse dans le langage d'assemblage généré, ne considérez pas les références comme un pointeur « marrant » sur 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.

Mis à jour le 19 octobre 2004 Aurelien.Regat-Barrel

L'état du référent (le référent est l'objet auquel la référence se rapporte) est modifié.

Le référent est la référence, donc toute modification faite à la référence est faite au référent. Au niveau du compilateur, une référence est une « lvalue », c'est-à-dire qu'il peut apparaître à la gauche d'un opérateur d'affectation.

Mis à jour le 19 octobre 2004 Cline

L'appel de fonction peut se trouver à la gauche d'un opérateur d'assignation.

Cette possibilité peut sembler étrange au premier abord. Par exemple, personne ne penserait que l'expression f() = 7; ait un sens. Par contre, si a est un objet de la classe Array, la plupart des gens trouveront que a[i] = 7; a un sens, même si a n'est qu'un appel de fonction caché (c'est en fait l'appel de l'opérateur Array::operator [](int), qui est l'opérateur d'indexation de la classe Array)

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Array { 
public: 
    int size() const; 
    float& operator[] (int index); 
    ... 
}; 
  
int main() 
{ 
    Array a; 
    for (int i = 0; i < a.size(); ++i) 
        a[i] = 7;    // cette ligne appelle Array::operator[](int) 
    ... 
}

Mis à jour le 10 février 2004 Cline

Ce n'est pas possible.

On ne peut pas séparer le référé de sa référence.

Contrairement aux pointeurs, lorsqu'une référence est liée à un objet, elle ne peut pas être réaffectée à un autre objet. La référence en elle-même n'est pas un objet (elle n'a pas d'identité : prendre l'adresse d'une référence retourne l'adresse du référent).

De ce point de vue, une référence est similaire à un pointeur constant

Code c++ : Sélectionner tout
int* const p

par opposition à un pointeur sur une constante

Code c++ : Sélectionner tout
const int* p

En dépit d'une certaine ressemblance, ne confondez pas les références et les pointeurs ; ce n'est pas du tout la même chose.

Mis à jour le 10 février 2004 Cline

Lorsque l'on compare le code exécutable généré lors de l'utilisation d'un pointeur et celui généré lors de l'utilisation d'une référence, on remarque qu'il est strictement identique.

Cependant la référence a l'énorme avantage sur le pointeur d'apporter une garantie de non nullité, et de respecter la constance.

De plus, on ne peut envisager de déclarer une référence qu'en lui indiquant directement l'objet dont elle se fait l'alias.
Ainsi, alors que Type * ptr; sera accepté, Type & ref; sera refusé.
De plus, lorsque, dans une fonction, on récupère un pointeur, on n'est pas toujours (rarement ?) assuré que ce pointeur est non NULL, il va donc falloir souvent le vérifier par un

Code c++ : Sélectionner tout
if (mon_pointeur!=NULL) {...}
Alors que lorsque l'on reçoit une référence, on est assuré que l'objet a été instancié du seul fait de cette clause de non nullité et que l'on n'a donc pas besoin de le tester. Les références assurent ainsi une précondition statique de l'appelant envers la fonction appelée.

Non seulement, cela allège et simplifie le code, mais également il arrive que ce test supplémentaire pèse lourd en terme de vitesse d'exécution.

En ce qui concerne la constance, elle va s'appliquer, pour les pointeurs, différemment en fonction de l'endroit où se trouve le mot clé const voir Comment se gère la constance avec les pointeurs? alors qu'avec une référence, les choses sont claires : si une référence est déclarée constante, cela signifie :
  • que l'on ne peut pas modifier l'objet ;
  • que l'on ne peut pas décider de se servir de la référence comme alias pour un autre objet.

En outre, il faut se souvenir que l'utilisation des pointeurs est énormément associée à la gestion dynamique de la mémoire (création avec new ou new[] et destruction respectivement avec delete ou delete[]), et que la gestion dynamique de la mémoire est le champs de mines par excellence qu'il s'agit d'éviter autant que possible. Voir Quels sont les dangers liés à l'utilisation des pointeurs ?

Or, il faut comprendre que le C++ fournit, au travers des différents conteneurs qu'il présente ou au travers de sa classe string, tout ce qu'il faut pour éviter à l'utilisateur de gérer dynamiquement la mémoire si ce n'est pas tout à fait indispensable (cf Quel conteneur choisir pour stocker mes objets ?).

Enfin, même si ce n'est qu'un détail, il faut avouer que le fait de garder exactement la même syntaxe lorsque l'on manipule une référence que lorsque l'on manipule l'objet lui-même est presque rassurant.

En conclusion, l'idéal en C++ sera toujours de manipuler des références (éventuellement constantes) partout où c'est possible sans devoir avoir recours à l'opérateur « & » (prendre l'adresse de) et de ne manipuler des pointeurs que lorsque l'on n'a vraiment pas le choix.

Les cas pour lesquels nous n'aurons pas le choix étant :
  • Lorsqu'il s'agit de passer un argument optionnel ou pouvant ne pas exister, on passe alors un pointeur NULL ;
  • Lorsqu'il s'agit d'avoir une référence vers un objet optionnel ou pouvant ne pas exister (on utilise alors un pointeur sur l'objet dont l'adresse initialisée est NULL) ;
  • Lorsque tu as besoin de créer un objet polymorphique et que le type de l'objet à créer se détermine selon les circonstances ;
  • Lorsque, confronté à une relation « conteneur à contenu », le contenu doit disposer d'une référence sur le conteneur ;
  • Lorsque l'on souhaitera pouvoir modifier l'objet auquel on se réfère.

Note : il existe Boost.Optional permettant de manipuler des objets comme s'ils n'étaient pas initialisés.

Note : malgré la garantie qu'une référence ne soit pas nulle, il existe des cas où elle peut référer à un objet qui a été détruit ou déplacé, son utilisation étant dès lors dangereuse. On pourra citer le cas du retour par référence d'un objet local :
Code c++ : Sélectionner tout
1
2
3
4
5
Type& fonction() 
{ 
    Type tmp; 
    return tmp; 
} // tmp est détruit
signalé par le compilateur par le message :
reference to a local variable 'tmp' returned

mais il existe des cas moins triviaux qui ne seront pas signalés.

Mis à jour le 6 juillet 2014 3DArchi koala01 Luc Hermitte r0d

Le terme handle est utilisé pour désigner n'importe quelle technique qui permet de manipuler un autre objet (un genre de pseudo pointeur généralisé). Ce terme est (volontairement) ambigu et peu précis.

L'ambiguïté est un avantage dans certains cas. Par exemple, au tout début du design vous ne serez peut-être pas prêt à adopter une représentation spécifique pour désigner les handles. Vous ne serez peut-être pas sûr du choix à faire entre les simples pointeurs, les références, les pointeurs de pointeurs, les références de pointeurs, ou encore des tableaux indicés, ou des tables de hachage, ou des bases de données ou n'importe quelle autre technique. Si vous savez que vous aurez besoin de quelque chose qui identifiera de façon unique un objet, appelez cette chose un handle.

Si votre but final est de permettre à une portion de code d'identifier/rechercher un objet spécifique d'une classe d'un certain type (par ex. Fred), vous devrez passer un handle sur Fred à cette portion de code. Le handle peut être une chaîne qui peut-être utilisée comme une clé dans une table de recherche bien connue. Par exemple, une clé dans

Code c++ : Sélectionner tout
std::map<std::string,Fred>

ou

Code c++ : Sélectionner tout
std::map<std::string,Fred*>

ou encore un entier qui sera un indice dans un tableau du genre

Code c++ : Sélectionner tout
Fred* array = new Fred[maxNumFreds]

ou tout simplement un pointeur sur Fred, ou n'importe quoi d'autre.

Les débutants pensent souvent en termes de pointeurs, mais en réalité, ils prennent un risque. Que se passe-t-il si l'objet Fred doit être déplacé ? Comment savoir quand il est sans risque d'effacer l'objet Fred ? Que se passe-t-il si l'objet doit être sérialisé ?
La plupart du temps, on aura tendance à ajouter de plus en plus de couches d'indirections pour gérer ces cas de figure. Par exemple, le handle sur Fred devrait être un Fred **, où le pointeur pointant sur Fred* est supposé ne jamais être déplacé, mais à un moment le pointeur doit être déplacé, on met seulement à jour le pointeur sur Fred *. Ou vous décidez que le handle devient un entier désignant l'objet Fred dans une table, etc.

Le fait est que nous utilisons le mot handle tant que nous ne savons pas le détail de ce que nous allons faire.

Une autre circonstance dans laquelle nous utilisons le mot handle est quand on préfère rester vague au sujet de ce que nous avons déjà fait (on utilise parfois le terme « cookie » pour cela, par ex. « Le programme passe un cookie qui est utilisé pour identifier de façon unique l'objet Fred adéquat »). La raison pour laquelle nous voulons (parfois) rester vague est de minimiser les effets de bord si les détails d'implémentations devaient changer. Par exemple, si quelqu'un change le handle qui était une chaîne qui servait à faire une recherche dans une liste de hachage en un entier qui sert à indicer une table, cela pourrait causer le changement de dizaines de milliers de lignes de code.

Pour faciliter la maintenance quand les détails de représentation d'un handle changent (ou tout simplement pour rendre le code plus lisible), nous encapsulerons le handle dans une classe. Cette classe surchargera souvent les opérateurs -> et * (comme le handle agit comme un pointeur, il semble logique qu'il ressemble à un pointeur).

Mis à jour le 10 février 2004 Cline

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.