[12] L'opérateur d'affectation

(Une partie de C++ FAQ Lite fr, Copyright © 1991-2002, Marshall Cline, cline@parashift.com)

Traduit de l'anglais par Fabrice Clerc

Les FAQs de la section [12]


[12.1] Qu'est-ce donc qu'une "auto-affectation"?
[Ajout récent (10/99): main() a maintenant un type de retour. Cliquez ici pour aller à la prochaine FAQ ayant été modifiée récemment.]

Une auto-affectation a lieu quand quelqu'un affecte un objet à lui-même. Exemple:

#include "Fred.hpp" // Déclaration de la classe Fred

void userCode(Fred∓ x)
{
x = x; // Auto-affectation
}

Bien évidemment, personne n'écrit du code pareil, mais parce que des pointeurs ou des références distinctes peuvent désigner le même objet (c'est l'aliasing), des auto-affectations peuvent avoir lieu derrière votre dos:

#include "Fred.hpp" // // Déclaration de la classe Fred

void userCode(Fred∓ x, Fred∓ y)
{
x = y; // C'est une auto-affectation si ∓x == ∓y
}

int main()
{
Fred z;
userCode(z, z);
}

[ Haut | Bas | Rechercher ]


[12.2] Pourquoi l'auto-affectation peut-elle poser problème?
Si vous ne prenez pas en compte le cas de l'auto-affectation, vous exposez les utilisateurs de vos classes à des bugs subtils qui peuvent avoir des conséquences désastreuses. Par exemple, l'affectation d'un objet de la classe ci-dessous à lui-même va poser un très gros problème :

class Wilma { };

class Fred {
public:
Fred() : p_(new Wilma()) { }
Fred(const Fred∓ f) : p_(new Wilma(*f.p_)) { }
~Fred() { delete p_; }
Fred∓ operator= (const Fred∓ f)
{
// Ce code n'est pas bon: il ne traite pas le cas de l'auto-affectation!
delete p_; // Ligne 1
p_ = new Wilma(*f.p_); // Ligne 2
return *this;
}
private:
Wilma* p_;
};

Si quelqu'un assigne un objet de type Fred à lui-même, la ligne 1 va détruire à la fois this->p_ et f.p_ puisque *this et f désignent ici le même objet. Juste derrière, la ligne 2 utilise *f.p_, mais cet objet n'est plus valide puisqu'il a été détruit. Inutile de vous dire que cette utilisation risque fort de s'avérer catastrophique.

Retenez qu'il est votre responsabilité, en tant qu'auteur de la classe Fred, de garantir que l'affectation d'un objet de type Fred à lui-même ne pose pas de problèmes. Ne partez pas du principe que les utilisateurs de vos classes ne feront jamais ce genre d'affectation. Et ce sera de votre faute si un objet de votre classe fait crasher le programme dans le cas où on l'affecte à lui-même.

Notez aussi que dans l'exemple ci-dessus, l'opérateur Fred::operator= (const Fred&) contient un autre bug: Si une exception est l ancée lors de l'évaluation de new Wilma(*f.p_) (par exemple., une exception plus-de-mémoire ou une exception lancée par le constructeur par copie de Wilma), this->p_ va se retrouver pointant sur de la mémoire qui n'est plus valide. La solution consiste à allouer les nouveaux objets avant de détruire les anciens.

[ Haut | Bas | Rechercher ]


[12.3] OK, OK, j'ai compris le problème de l'auto-affectation. Et alors quelle est la solution?
[Ce paragraphe a été revu récemment (1/00), merci à Stan Brown pour ses commentaires. Cliquez ici pour aller à la prochaineFAQ ayant été modifiée récemment.]

Vous devez vous poser la question de l'auto-affectation à chaque fois que vous créez une classe. Mais ça ne veut pas dire qu'il est nécessaire que vous ajoutiez du code à toutes vos classes: ajouter du code n'est pas utile si vos objets peuvent être assignés à eux-mêmes sans que cela pose un problème.

Si vous devez modifier votre opérateur d'affectation, voici une technique simple et efficace :

Fred& Fred::operator= (const Fred& f)
{
if (this == &f) return *this; // Traite correctement le cas de l'auto-affectation

// Ici se trouve le code normal de l'opérateur d'affectation...

return *this;
}

Ce test explicite n'est pas toujours nécessaire. Par exemple, si vous vouliez corriger l'opérateur d'affectation de la FAQ précédente de façon à ce que les exceptions lancées par new et/ou les exceptions lancées par le par le constructeur de copie de la classe Wilma soient gérées correctement, vous écririez le code suivant. Et notez que ce code a pour (agréable) effet de bord de traiter correctement le cas de l'auto-affectation:

Fred& Fred::operator= (const Fred& f)
{
// Ce code traite correctement (même si c'est implicite) le cas de l'auto-affectation
Wilma* tmp = new Wilma(*f.p_); // Pas de problème si une exception était levée ici
delete p_;
p_ = tmp;
return *this;
}

Dans un cas comme ci-dessus (où l'auto-affectation est sans danger mais inefficace), certains programmeurs veulent quand même ajouter "if (this == &f) return *this;" pour obtenir de meilleures performances dans le cas d'une auto-affectation. C'est la plupart du temps un mauvais choix, car si une auto-affectation a lieu une fois sur mille, alors le if consommera inutilement des cycles processeur dans 99,9% des cas.

[ Haut | Bas | Rechercher ]


E-mail Marshall Cline Ecrire à l'auteur, au traducteur, ou en savoir plus sur la traduction.
C++ FAQ Lite fr | Table des matières | Index | A propos de l'auteur | © | Téléchargez votre propre copie ]
Dernière révision Sun Apr 13 23:53:58 PDT 2003