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



Qu'est-ce qu'une référence ?
auteur : Marshall Cline
Une référence est un alias, un nom alternatif pour un objet.

Les références sont souvent utilisées lors du passage de paramètres par référence.

void swap(int& i, int& j) { int tmp = i; i = j; j = tmp; } int main() { int x, y; ... swap(x,y); ... }
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. Au fond, une référence i vers un object 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.


Que se passe-t'il si on assigne une autre valeur à la référence ?
auteur : Marshall Cline
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.


Que se passe-t-il en cas de retour d'une référence lors de l'appel d'une fonction ?
auteur : Marshall 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, meme si a[i] 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)

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; // This line invokes Array::operator[](int) ... }

Que signifie object.method1().method2() ?
auteur : Marshall Cline
C'est l'enchaînement des appels de ces fonctions, c'est pourquoi cela s'appelle le chaînage des méthodes.

La première chose qui est exécutée est objet.method1(). Cela renvoie un objet ou une référence sur un objet (par ex. method1() peut se terminer en renvoyant this), ou n'importe quel autre objet. Appelons cet objet retourné "ObjetB". Ensuite, ObjetB devient l'objet auquet est appliqué method2().

L'utilsation la plus courante du chaînage de méthodes est la libraire iostream.

cout << x << y ;
fonctionne parce que

cout << x
est une fonction qui retourne cout.


Comment faire pour modifier une référence de façon à ce qu'elle désigne un autre objet ?
auteur : Marshall Cline
Ce n'est pas possible.

On ne peut pas séparer le réferent 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

int* const p
par opposition à un pointeur sur une constante

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


Quand utiliser des références et quand utiliser des pointeurs
auteur : Marshall Cline
Utilisez les références aussi souvent que possible, et les pointeurs quand vous le devez.

Les références sont habituellement préférables aux pointeurs, à moins que vous ne deviez la réallouer à un moment donné. Cela veut dire que les références sont plus utiles dans les interfaces publiques des classes. Les références apparaissent typiquement dans les interfaces, alors que les pointeurs sont utilisés à l'intérieur.

Exception à ce qui vient d'être dit : lorsqu'un paramètre ou la valeur de retour d'une fonction a besoin d'une référence "sentinelle". Ceci est habituellement mieux fait en retournant ou recevant un pointeur et en donnant à NULL cette signification particulière. Les références devraient toujours désigner un objet et jamais un pointeur NULL déréférencé).

Note : les vieux programmeurs C n'apprécient pas d'utiliser des références étant donné qu'il attribuent une sémantique à la référence qui n'est pas claire dans le code appelant. Mais, après une certaine pratique du C++, certains réalisent qu'il s'agit d'une forme de masquage d'information, ce qui est plus un bien qu'un défaut.
Par ex. : les programmeurs devraient écrire le code dans le langage du problème plutôt que dans le langage de la machine.


Qu'est-ce qu'un handle sur un objet ? une référence ? un pointeur ? un pointeur sur un pointeur ?
auteur : Marshall Cline
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 hashage, 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

std::map<std::string,Fred>
ou

std::map<std::string,Fred*>
ou encore un entier qui sera un indice dans un tableau du genre

Fred* array = new Fred[maxNumFreds]
ou tout simplement un pointeur sur Fred, ou n'importe quoi d'autre.

Les débutants pensent souvent en terme 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 qaund il est sans risque d'effacer l'object 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ésigant 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 mont handle est quand on prefè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 hashage 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).



Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduit en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline