[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 ...)
- void f1(const std::string& s); // passage par const-reference
- void f2(const std::string* sptr); // passage par const-pointeur
- void f3(std::string s); // passage par valeur
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 ...
- void g1(string& s); // passage par reference non const
- void g2(string* sptr); // passage par pointeur non const
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).
-
const Fred * p signifie que "p pointe sur un const Fred"
-- c'est-à-dire que l'objet de type Fred ne peut pas être changé par l'intermdiaire de p.
-
Fred * const p) signifie que "p) est un const pointeur sur Fred)" -- c'est-à-dire que vous pouvez changer l'objet de type
Fred) par l'intermdiaire de p), mais que vous ne pouvez pas changer
le pointeur p) lui-même.
-
const Fred * const p) signifie que "p est un const pointeur sur un
const Fred)" -- c'est-à-dire que vous ne pouvez pas changer le pointeur
p) lui-même, pas plus que vous ne pouvez changer l'objet de type
Fred) via p).
[ 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 ]
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