[18] Utilisation correcte de const

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

Traduit de l'anglais par Jérôme Lecomte

Les FAQs de la section [18]


[18.1] Qu'est-ce que "l'utilisation correcte de const" ?
Une bonne chose. En utilisant le mot-clé const pour empêcher des objets constant de changer (muter). Par exemple, si vous vouliez créer une fonction f() qui reçoit une chaîne de caractères, si de plus vous voulez promettre à la fonction appelante de ne pas changer l'argument de type chaîne de caractères qu'elle passe à f(), vous pouvez déclarer l'argument string) de f() comme ...) Lors du passage par const-réference et du passage par const-pointeur, toutes tentatives de changer l'argument de type string) à l'intérieur de f() se traduirait par une erreur à la compilation. Ce contrôle est fait entièrement au moment de la compilation: il n'y a là aucun coût en terme de mémoire ou vitesse d'excution en contre partie de l'utilisation de const. Dans le cas du passage par valeur f3(), la fonction appelée obtient une copie de la chaîne de caractère en argument. Ceci signifie que f3() peut changer sa copie locale, mais la copie est détruite quand f3() retourne. Par conséquent f3() ne peut pas changer l'objet chaîne de caractères. Si au contraire vous vouliez créer une fonction g() qui reçoit une chaîne de caractères, et que vous vouliez faire savoir que l' argument de g() peut être modifié (peut muter). Dans ce cas-ci vous pourriez écrire g() ainsi ... L'absence de const dans ces fonctions dit au compilateur que la fonction peut (mais ça n'est pas une exigence) de changer la chaîne de caractères en argument. Ainsi les fonctions appelantes peuvent passer une chaîne de caractères à n'importe quelle f() fonctions, mais seulement f3() (celui qui reoit son paramêtre "par valeur") peut passer la chaîne de caractêres à g1() ou g2(). Si f1() ou f2() ont besoin d'appeler l'une ou l'autre des fonctions g(), une copie locale de la chaîne de caractères doit être passée vers g(); le paramtêre de f1() ou f2() ne peut pas être directement passé vers g(). Par exemple
    void g1(string& s);

    void f1(const string& s)
    {
      g1(s);  // erreur au moment de la compilation car s est const

      string localCopy = s;
      g1(localCopy);  // CORRECT puisque localCopy n'est pas const
    }
Naturellement dans le cas ci-dessus, tout changement à l'intérieur de g1() est en fait appliqué à l'objet localCopy de f1(). En particulier, il n'y a pas de modification du paramêtre passé par réference const à f1().

[ Haut | Bas | Rechercher ]


[18.2] En quoi "l'utilisation correcte de const" est liée à la sureté de type ordinaire
Déclarer un paramêtre const est juste une forme particulière de la sûreté de type qu'offre le C++. C'est presque comme si un object de type const std::string), par exemple, etait d'une classe différente d'un std::string) ordinaire, puisqu'il manque à la version const les diverses exécutions modifiante de la variante non-const (songez simplement qu'une const std::string) n'a pas d'opérateur d'affectation par exemple). Si vous trouvez que les aides ordinaires de sûreté de type vous aide à obtenir des systmes corrects (elles le font; particulirement dans de grands systèmes), vous trouverez que l'utilisation correcte de const vous aide galement.

[ Haut | Bas | Rechercher ]


[18.3] Devrais-je essayer d'utiliser correctement const plûtôt tôt, ou plûtôt tard?

Au tout, tout, tout début.

Utiliser const correctment après coup cause un effet de boule neige: chaque const ajouté "par ici" exige quatre const de plus "par là."

[ Haut | Bas | Rechercher ]


[18.4] Qu'est-ce que const Fred* p signifie?
Ca signifie que p pointe sur un objet de classe Fred, mais que p ne peut pas être utilisé pour changer l'objet de type Fred (naturellement p) peut aussi être NULL. Par exemple, si la classe Fred) a une fonction membre const appelée inspect(), p->inspect() est CORRECT. Mais si la classe Fred) a une fonction membre non-const appelée mutate(), p->mutate() est une erreur (l'erreur est décelée par le compilateur; aucun essai d'exécution n'est fait, ce qui signifie que const ne ralentit pas votre programme.

[ Haut | Bas | Rechercher ]


[18.5] Quelle est la différence entre "const Fred* p", "Fred * const p", et "const Fred * const p"?
Vous devez lire les déclarations de pointeur de droite à gauche (NDT: les adjectifs sont placé avant) le nom qu'ils qualifient en anglais).

[ Haut | Bas | Rechercher ]


[18.6] Qu'est-ce que "const Fred& x)" signifie?

Cela signifie que x est un alias pour un object de type Fred, mais que x) ne peut pas être utilisé pour modifier l'object de type Fred.

Par exemple, si la classe Fred) a une fonction membre const appelée inspect(), x.inspect() est CORRECT. Mais si la classe Fred a une fonction membre non-const appelée mutate(), x.mutate() est une erreur (l'erreur est décelée par le compilateur; aucun essai d'exécution n'est fait, ce qui signifie que const ne ralentit pas votre programme.

[ Haut | Bas | Rechercher ]


[18.7] Est-ce que "Fred& const x)" signifie quelque chose?
Non, ça n'a pas de sens. Pour avoir une idée de ce que signifie cette déclaration, _LINK( [[const-correctness.html#faq-18.5"> vous devez lire de droite à gauche). Donc "Fred& const x)" signifie que "x) est une const réference à un Fred)". Mais c'est redondant puisque que les réferences sont toujours const: vous ne pouvez pas changer le réferant d'une réference. Jamais. Avec ou sans const. En d'autres termes, "Fred& const x)" est fonctionellement équivalent à "Fred& x)". Puisque vous n'obtenez rien en ajoutant le const après le &), vous ne devriez pas l'utiliser pour éviter de créer des problèmes. I.e. le const peut amener certaines personnes à penser que l'objet de type Fred) est const, comme si vous aviez "const Fred& x)".

[ Haut | Bas | Rechercher ]


[18.8] Que signifie "Fred const& x)"?
"Fred const& x)" est fonctionellement quivalent à "const Fred& x)". Le problème avec l'utilisation de "Fred const& x)" (avec le const avant) est qu'il pourrait facilement être mal tappé en une erreur "Fred& const x" (avec le const aprês).

Amliorez pour utiliser simplement le const Fred& X.

[ Haut | Bas | Rechercher ]


[18.9]Que signifie "Fred const* x)"?

Fred const& x is functionally equivalent to const Fred& x. However, there is considerable disagreement as to which should be used.

Some gurus recommend Fred const& x, the idea being that it's easier to read right-to-left ("x is a reference to a const Fred"). However that reading is technically wrong, and more importantly, the mere fact that many programmers need this particular FAQ shows that Fred const& x will cause some confusion, especially with that all-important (and large!) pile of programmers toward the middle of the bell curve.

So here's a recommendation: program to whatever level is appropriate for your organization's average maintenance programmer. Yes, that's right: the average maintenance programmer. Not the gurus, not the morons, but the average maintenance programmer. Unless you're willing to shoulder the burden to train your maintenance programmers and/or fire them and hire new ones, you have to make sure that they can understand the code you write. If that means using const Fred& x simply because that's the form used in most books, so be it. If not, then that's fine too.

Note: you'll have to overcome a certain amount of inertia if you decide to go with Fred const&. That's because, as of this date, most C++ programmers use const Fred&. Most books use that syntax, most programmers learned C++ with that syntax, and most still use that syntax. That doesn't mean const Fred& is better, but it does mean you will have to accept some confusion and mistakes during the transition and when you hire new people. Some people are convinced the benefits outweigh the costs; most, apparently, do not.

BTW if you do decide to move the const as far to the right as possible, i.e., to use Fred const& x, do something to make absolutely sure they don't mis-type it as the nonsensical " Fred &const x" .

[ Haut | Bas | Rechercher ]


[18.10] Qu'est-ce qu'une fonction membre "const" ?
Une fonction membre qui inspecte (par opposition a modifie) l'objet implicite. Une fonction membre const est reperable grâce au const suffixe juste apres la liste des paramètres. Les fonctions membres avec le suffixe const sont appelées "fonctions membre const" ou "inspecteurs". Les fonctions membres sans un suffixe const sont appelées "fonctions membres non-const" ou "mutators".
class Fred {
public:
  void inspect() const;   // Ce membre promet de NE PAS modifier *this
  void mutate();          // Ce membre peut eventuellement modifier *this
};

void userCode(Fred& changeable, const Fred& unchangeable)
{
  changeable.inspect();   // OK: ne modifie pas l'objet
  changeable.mutate();    // OK: modifie un objet modifiable

  unchangeable.inspect(); // OK: ne modifie pas un objet non-modifiable
  unchangeable.mutate();  // ERROR: tentative de modifier un objet non-modifiable
}

L'erreur dans [[unchageable.mutate() est saisie à la compilation. Le programme n'est pas pénalisées à l'execution que ce soit en utilisation mémoire ou en vitesse.

Le const apres la fonction membre inspect() signifie que l'état abstrait) (visible par le client) ne changera pas. C'est légerement différent par rapport à promettre que la respresentation en bits de l'objet ne changera pas. Les compilateurs C++ ne peuvent pas choisir l'interpretation "au bit près" à condition qu'ils soient capables de résoudre le problème d'aliassage, qui normalement ne peut pas être résolu (ie. un alias non-const pourrait exister lequel modifierait l'état de l'objet). Un autre point de vue (important) sur ce problème d'aliassage: pointer sur un objet avec un pointeur sur const ne garantie pas que l'objet ne changera pas; cela promet au plus que l'objet ne changera pas via ce pointeur).

[ Haut | Bas | Rechercher ]


[18.11] Que faire si je veux mettre à jour une donné membre à l'interieur d'une fonction membre const?

Utilisez mutable), ou utiliser const_cast).

Un faible pourcentage des inspecteurs doivent faire des changements inoffensifs à des donnés membre (e.g. un objet List) peut vouloir cacher la dernière recherche dans l'espoir d'amméliorer la performance de la recherche suivante). En disant "inoffensif", je veux dire que les changements ne sont pas visible depuis l'interface de l'objet (dans le cas contraire, la fonction membre ne serait pas un inspecteur).

Quand cela arrive, la donné membre qui va être modifiée doit être marquée mutable) (mettez le mot clé mutable) avant la déclaration de la donnée membre. Si votre compilateur ne supporte pas le mot clé mutable), vous pouvez transtyper this) avec le mot clé const_cast). Eg. dans List::lookup() const), vous pourriez faire,

List * self = const_cast<List *>(this);

Après cette ligne, self) aura les mème bits que this) (e.g. self == this), mais self) est un List *) au lieu d'un const List *) avant. Par conséquent, vous pouvez utiliser self) pour modifier l'objet pointé par this).

[ Haut | Bas | Rechercher ]


[18.12] Est-ce que const_cast) signifie la perte d'opportunités d'optimiser
En théorie oui; en pratique non. Même si le langage déconseille const_cast), le seul moyen d'éviter de vider le cache registre lors d'un appel à une fonction membre const serait de résoudre le problème d'aliassage (ie. de prouver qu'il n'y a pas de pointeur non-const sur l'objet). Ceci arrive seulement en de rares occasions (lorsque l'objet est construit dans la portée de l'invocation de la fonction membre const, et lorsque toutes les invocations de membres non-const entre la construction de l'objet et l'invocation de la fonction membre const ne sont pas virtuelles, et lorsque chacune de ces invocations est aussi inline), et lorsque le constructeur lui-mêmeme est déclaré inline), et lorsque toutes les fonctions membres appelées par le constructeur sont aussi inline).

[ Haut | Bas | Rechercher ]


[18.13] Pourquoi est-ce que le compilateur me laisse modifier un int) après que j'ai pointé dessus avec un const int *)?

Parce que "const int * p)" signifie "p) promet de ne pas changer *p)", et pas "*p) promet de ne pas changer".

Faire pointer un const int *) sur un int) ne const-ifie pas le int) pointé. Le int) ne peut plus être changé par le const int *, mais si quelqu'un d'autre a un int *) (note: non const) qui pointe ("aliasse") the même int), alors le int * peut être utilisé pour changer le int). Par exemple:

    void f(const int* p1, int* p2)
    {
      int i = *p1;         // Obtient la valeur (originale) de *p1
      *p2 = 7;             // Si p1 == p2, ceci changera aussi *p1
      int j = *p1;         // Obtient lai valeur (eventuellement nouvelle) de *p1
      if (i != j) {
        cout "*p1 a change, mais il n'a pas change via le pointeur p1!\n";
        assert(p1 == p2);  // C'est le seul moyen par lequel *p1 pourrait etre different
      }
    }

    main()
    {
      int x;
      f(&x, &x);           // Cela est parfaitement legal (et meme moral!)
    }

Notez que main() et f(const int *, int *) pourraient être dans différentes unités de compilation compilées différents jours de la semaine. Dans ce case, il n'y a pas de moyen pour le compilateur de détecter l'aliassage à la compilation. Par conséquent, il n'y a pas de moyen de créer une règle qui interdit ce genre de chose. En fait, nous ne voudrions même pas créer une telle règle, puisque en général on considère que c'est une qualité de pouvoir avoir plusieur pointeurs pointant sur la même chose. Le fait que l'un de ces pointeur promette de ne pas changer la "chose" sous-jacente est une promesse faite par le pointeur); et non) une promesse faite par la "chose".

[ Haut | Bas | Rechercher ]


[18.14] Est-ce que "const Fred* p" signifie que *p ne change pas?

No! c'est lié à la FAQ sur l'aliassage des pointeur int.

"const Fred * p" signifie que Fred ne peut pas être modifié via le pointeur p), mais n'importe quel pointeur non const qui aliasse peut être utilisé pour changer l'objet Fred). Par exemple, si vous avez deux pointeurs "const Fred * p)" et "Fred * q)" qui pointent sur le même objet Fred) (aliassé), le pointeur q peut être utilisé pour changer l'objet Fred mais le pointeur p ne peut pas,

    class Fred {
    public:
      void inspect() const;   // A fonciton membre const
      void mutate();          // A fonction membre non-const
    };

    main()
    {
      Fred f;
      const Fred* p = &f;
            Fred* q = &f;

      p->inspect();    // OK: pas de changement via p
      p->mutate();     // Erreur: ne peut pas changer *p via p

      q->inspect();    // OK: q est autorise a inspecter l'objet
      q->mutate();     // OK: q est autorise a modifier l'objet
    }

[ Haut | Bas | Rechercher ]


[18.15] Why am I getting an error converting a Foo** ? const Foo**?

Because converting Foo** ? const Foo** would be invalid and dangerous.

C++ allows the (safe) conversion Foo* ? const Foo*, but gives an error if you try to implicitly convert Foo** ? const Foo**.

The rationale for why that error is a good thing is given below. But first, here is the most common solution: simply change const Foo** to const Foo* const*:

 class Foo { /* ... */ };

 void f(const Foo** p);
 void g(const Foo* const* p);

 int main()
 {
   Foo** p = /*...*/;
   ...
   f(p);  // ERROR: it's illegal and immoral to convert Foo** to const Foo**
   g(p);  // OK: it's legal and moral to convert Foo** to const Foo* const*
   ...
 }
The reason the conversion from Foo** -> const Foo** is dangerous is that it would let you silently and accidentally modify a const Foo object without a cast:
 class Foo {
 public:
   void modify();  // make some modify to the this object
 };

 int main()
 {
   const Foo x;
   Foo* p;
   const Foo** q = &p;  // q now points to p; this is (fortunately!) an error
   *q = &x;             // p now points to x
   p->modify();         // Ouch: modifies a const Foo!!
   ...
 }
Reminder: please do not pointer-cast your way around this. Just Say No!

[ 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:54:09 PDT 2003