jeudi 3 juin 2010

Des pièges de l'utilisation des références C++

Le langage C++ a introduit la notion de "référence", en sus des pointeurs du langage C. Les références permettent notamment la mise en oeuvre du polymorphisme, élément fondamental de la POO. De ce fait, on a parfois tendance à les considérer comme des "pointeurs-bis", permettant juste une syntaxe plus légère tout en ne "trimballant" que l'adresse de l'objet. Par exemple, lors d'un passage d'argument à une fonction:
void MaFonction( const MaClasse& objet )
{
   objet.FaireCeci();
}
Il y a cependant une petite subtilité, qui peut entraîner des erreurs. Cette particularité peu évidente pour le programmeur novice peut être résumée par la phrase:

La référence EST l'objet

Ce point clé trouve son illustration dans un cas fréquent, à savoir la permutation de deux objets selon une condition. La méthode naïve consiste à faire une permutation circulaire:
   MaClasse objet1;
   MaClasse objet2;
   ...
   if( condition )
   {
      MaClasse objetTemp( objet1 );
      objet1 = objet2;
      objet2 = objetTemp;
   }

Mais cette solution est évidemment peu adaptée en pratique: si les objets sont volumineux (comprendre:plusieurs ko ou plus), les performances sont médiocres: 3 copies de l'objet sont nécessaires.

La STL fournit aussi une solution: l'algorithme std::swap(). Mais en pratique, il fait la même chose.

On est alors tenté de travailler sur l'adresse de l'objet, et on pense alors naturellement aux pointeurs:
   MaClasse objet1;
   MaClasse objet2;

   MaClasse* p1 = &objet1;
   MaClasse* p2 = &objet2;

   if( condition )
   {
      p2 = &objet1;
      p1 = &objet2;
   }

// attention, dans la suite on doit travailler avec l'opérateur '->'
p1->FaireCeci();
p2->FaireCela();

Ceci fonctionne très bien.
Le piège consiste alors à se dire "les pointeurs, c'est mal, remplaçons tout ça par des références, c'est bien plus dans l'esprit du C++". Soit:
   MaClasse objet1;
   MaClasse objet2;

   MaClasse& r1 = objet1;
   MaClasse& r2 = objet2;

   if( condition )
   {
      r2 = objet1;
      r1 = objet2;
   }

// Ouf! Maintenant, je peux me passer de '->' et travailler avec '.'
   r1.FaireCeci();
   r2.FaireCela();

Et non! Le code ci-dessus est faux. En effet, comme indiqué précédemment, la référence EST l'objet. Donc si 'condition' est vrai, la première affectation (r2 = objet1;) écrase objet2, vu que r2 a été initialisé sur objet2: r2 et objet2 désignent le même espace mémoire. Son contenu est donc perdu, et on se retrouve avec deux objets identiques.

En conclusion, se souvenir que bien que leur utilisation soit bien moins fréquente que en C, l'utilisation des pointeurs reste parfois utile en C++.

A voir aussi:
* La FAQ Developpez
* LaFAQ de Marshall Cline, au paragraphe 8.5:
How can you reseat a reference to make it refer to a different object?
No way. You can't separate the reference from the referent.



Edit 201204: Comme noté ci-dessus, l'algo std::swap() réalisait des copies des objets, ce qui est inacceptable dans certains cas. La nouvelle norme du C++ (C++11) fournit l'algo std::move() qui s'appuie sur le fait que la "r-value" (objet temporaire situé à droite de l'opérateur d'affectation) n'est jamais réutilisé, et qu'on peut donc directement copier l'adresse. On peut donc désormais "encore plus" éviter les pointeurs bruts. Voir les détails ici.

1 commentaire:

  1. Salut,

    Parmi les défauts des pointeurs, il y a également le fait que rien ne garantit la validité de la zone mémoire pointée par un pointeur.

    L'utilisation exclusive des références, au contraire, garantit totalement leur emploi.

    Mais effectivement, faire un swap juste avec juste des références et sans créer un 3ième laron dans l'histoire, je vois pas comment faire. Mais il est vrai je suis un peu rouillé côté programmation.

    Ceci étant dit, on peut se poser la question de l'intérêt d'intervertir le contenu de 2 objets. Dans quel but ? Si c'est pour faire du tri, alors on intervertit pas le contenu des objets mais leur position dans la liste.

    Pas simple tout ça. En tous cas, tes articles sont sympas.

    RépondreSupprimer