OVH Cloud OVH Cloud

Bon usage du qualificateur "const" pour fonctions membres

22 réponses
Avatar
bylybob
Bonjour a tous, je suis pris d'un doute sur le bon usage du
qualificateur "const" pour les fonctions membres dans certains cas
particuliers: objects transitoires dont le but est simplement de
calculer des informations sur un autre object.

Prenons l'exemple de calcul scientifique, typiquement il y a un
"maillage" (par exemple un ensemble de triangles munis de proprietes
geometriques et mecaniques) et un "calculateur" (par exemple elements
finis munis de parametres de calculs). Le "calculateur" calcule (!) et
stocke les resultats sur la grille.

Le "calculateur" a naturellement une fonction "bool calcule();" qui ne
change pas les parametres de calcul mais va rajouter certainnes
proprietes (le resultat du calcul) sur le maillage.

Alors "bool calcule();" ou "bool calcule() const;"?

Merci,

bylybob


class calculateur {
public:
calculateur(maillage* m, <parametre de calcul...>);

bool calcule() const { // Const ou pas const?
//- Calcule des trucs...
m->stocke(resultat);
//...
}

private:
struct Impl; // Details
//...
};

10 réponses

1 2 3
Avatar
James Kanze
On Jun 15, 10:47 am, "Patrick 'Zener' Brunet"
wrote:

[...]
J'ai trouvé dans des grammaires BNF que mutable est un
storage-qualifier et pas un type-modifier ? Il ne serait donc
pas vraiment le contraire de const ?


Pas du tout. Il ne s'applique aux objets (membres). On ne peut
pas avoir un pointeur vers un mutable, par exemple (qui n'aurait
évidemment pas de sens, étant donné que si le pointeur n'est pas
pointeur vers const, on peut bien effectuer des mutations à
travers lui).

Pourtant je l'utilise exclusivement dans ce sens et avec
succès...


Tu fais des :
int mutable* pMutableInt ;
?

Il est vrai (c'est la seconde question) que j'expérimente
ceci: J'aime bien formuler mes déclarations en plaçant les
type-modifiers systématiquement à droite, plutôt qu'à gauche
pour le premier. Donc ils ne sont jamais mêlés aux
storage-qualifiers.


Il y a bien une différence :

const int* pi ; // C'est l'int qui est const
mutable int* pi ; // C'est le pointeur qui est mutable

À cet égard, il se comporte exactement comme un « storage
class » ; il concerne l'objet qu'on déclare, et seulement
l'objet qu'on déclare, et qu'il ne fait pas par partie du type,
ni qu'on peut avoir des types genre pointeur à mutable.

Ca se passe très bien *a priori* et je trouve que ce schéma
systématique est plus logique (et facilite la génération
automatique de code):

plutôt que:

const int ki;
const int * const kpki;
int * const * const kpkpi;

je préfère:

int const ki;
int const * const kpki;
int * const * const kpkpi;



Certes. Je crois que c'est devenu la façon préféré de la
majorité des experts.

Y a-t-il des compilos modernes que ça peut gêner ?


Il n'y a même pas les anciens que ça pourrait gêner. L'ordre des
éléments dans le « declaration-specifier-seq » est indifférent,
et ça, depuis les premiers jours de C. (Et en dehors des
« declaration-specifier-seq », le const ne peut que suivre ;
toute autre position est illégale, et l'a toujours été.) Donc,
en C original, quelque chose comme :
int const short static unsigned i = 43 ;
est parfaitement légal. (Il me semble avoir lu quelque part que
la présence d'un « storage class », c-à-d le static ici, en
dehors du debut est deprécié. En C ; je ne crois pas que c'est
le cas en C++.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34

Avatar
James Kanze
On Jun 14, 11:50 pm, Fabien LE LEZ wrote:
On Sat, 14 Jun 2008 13:21:47 -0700 (PDT), James Kanze
:

amené la STL à passer des objets fonctionnels
par valeur, plutôt que par référence.)


N'était-il pas possible de faire deux versions de chaque
algorithme, une avec passage du foncteur par référence non
const, et une autre avec passage du foncteur par référence
const ?


Je ne suis pas sûr. Si elles n'étaient pas des templates,
certainement, mais c'est possible que ça pose des problèmes dans
la déduction de type (ou que ça les posait lorsqu'on développait
la STL).

Accessoirement, le code ci-dessous permet-il de passer une
référence non-const à un algorithme ?

template <class Iter, class Function>
void for_each_reference (Iterfirst if, Iterlast il, Function& f)
{
std::for_each<Iter, Function&> (if, il, f);
}


Je ne sais pas. J'avoue que les règles ici sont trop compliquées
pour moi.

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Patrick 'Zener' Brunet
Bonsoir.

"James Kanze" a écrit dans le message de news:

On Jun 15, 10:47 am, "Patrick 'Zener' Brunet"
wrote:

[...]
J'ai trouvé dans des grammaires BNF que mutable est un
storage-qualifier et pas un type-modifier ? Il ne serait donc
pas vraiment le contraire de const ?


Pas du tout. Il ne s'applique aux objets (membres). On ne
peut pas avoir un pointeur vers un mutable, par exemple
(qui n'aurait évidemment pas de sens, étant donné que si le
pointeur n'est pas pointeur vers const, on peut bien effectuer
des mutations à travers lui).

Pourtant je l'utilise exclusivement dans ce sens et avec
succès...


Tu fais des :
int mutable* pMutableInt ;
?


J'ai été imprécis en effet:
Effectivement, mutable s'applique au C++ et ne présenterait aucun intérêt
dans la déclaration d'une donnée globale ou automatique.

Je l'utilise (comme dans l'exemple que je donnais pour la classe Date) pour
rendre modifiable un membre d'une classe susceptible de déclaration const.
En ce sens, il "inverse l'effet de const" au niveau d'un membre (dans
l'exemple: un cache dont la mise à jour durant un calcul interne ne
représente pas une modification de l'objet au sens de sa sémantique
observable)...

Mais comme tu le précises:

[...]
Il y a bien une différence :

const int* pi ; // C'est l'int qui est const
mutable int* pi ; // C'est le pointeur qui est mutable


Donc on ne peut pas a priori faire le même genre d'acrobaties qu'avec const:

int * const kpi;
int const * pki;

mais pas (dans une classe):

int * mutable mpi; // passerait comme attendu
int mutable * pmi; // là c'est râté...

Ce qui ne présenterait d'ailleurs qu'un intérêt théorique...
Pour des raisons d'optimisation entre dans les relations privées entre des
classes intimement liées, j'utilise des accès directs par références, mais
là...
Je n'arrive pas à trouver un exemple réaliste dans lequel un objet aurait à
trifouiller ainsi dans un autre à travers un pointeur ou une référence qui
de plus passe outre un modifieur const.

Donc dans la notation "à droite", mutable ne doit apparaître qu'à la
position la plus à droite, sinon c'est de la tromperie.
Cela explique la différence dans la grammaire.

OK, donc ça me convient très bien. Merci.

[...]

en C original, quelque chose comme :
int const short static unsigned i = 43 ;
est parfaitement légal. (Il me semble avoir lu quelque part
que la présence d'un « storage class », c-à-d le static ici,
en dehors du debut est deprécié. En C ; je ne crois pas
que c'est le cas en C++.)


C'est limite sabotage tout de même...
Si i est persistant, donc non réinitialisé à chaque appel, il vaut mieux que
ça se voie bien :o)

--
Cordialement.
--
* Patrick BRUNET www.ipzb.fr
* E-mail: lien sur http://zener131.free.fr/ContactMe


Avatar
James Kanze
On Jun 15, 8:59 pm, "Patrick 'Zener' Brunet"
wrote:
Mais comme tu le précises:

[...]
Il y a bien une différence :

const int* pi ; // C'est l'int qui est const
mutable int* pi ; // C'est le pointeur qui est mutable


Donc on ne peut pas a priori faire le même genre d'acrobaties
qu'avec const:

int * const kpi;
int const * pki;

mais pas (dans une classe):

int * mutable mpi; // passerait comme attendu
int mutable * pmi; // là c'est râté...


Au contraire, c'est le premier qui ne passe pas. Le « mutable »
peut prendre les mêmes positions que « static » ou « extern »
dans une declaration, c-à-d n'importe où dans la
« declaration-specifier-seq », mais seulement dans elle.

Ce qui ne présenterait d'ailleurs qu'un intérêt théorique...
Pour des raisons d'optimisation entre dans les relations
privées entre des classes intimement liées, j'utilise des
accès directs par références, mais là...
Je n'arrive pas à trouver un exemple réaliste dans lequel un
objet aurait à trifouiller ainsi dans un autre à travers un
pointeur ou une référence qui de plus passe outre un modifieur
const.

Donc dans la notation "à droite", mutable ne doit apparaître
qu'à la position la plus à droite, sinon c'est de la
tromperie.


La notation « à droite » ne concerne en fait que les
« cv-qualifier », c-à-d const et volatile. Parce que ce sont les
seuls qui peuvent servir dans le « declarator », les seuls qui
interviennent sur le type, et les seuls qui peuvent s'appliquer
à plusieurs niveaux (soit au pointeur, soit au pointé).

[...]
en C original, quelque chose comme :
int const short static unsigned i = 43 ;
est parfaitement légal. (Il me semble avoir lu quelque part
que la présence d'un « storage class », c-à-d le static ici,
en dehors du debut est deprécié. En C ; je ne crois pas
que c'est le cas en C++.)


C'est limite sabotage tout de même... Si i est persistant,
donc non réinitialisé à chaque appel, il vaut mieux que ça se
voie bien :o)


C'est sûr que la tradition a toujours mis les « storage class
specifiers » au début de la declaration. Ça n'a jamais été
obligatoire, en revanche -- même dans K&R I, l'ordre des
spécificateurs dans le « declaration-specifier-seq » est libre
et sans signification (pour le compilateur). En C99, en revanche
« The placement of a storage-class specifier other than at the
beginning of the declaration specifiers in a declaration is an
obsolescent feature. » (En C99, les storage class specifiers
sont typedef, extern, static, auto et register. C++ traite
typedef à part, et aussi auto dans la nouvelle norme, mais y
ajoute mutable. Mais autant que je sache, C++ n'a pas
l'intention de deprécier les positions autre qu'au debut.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


Avatar
Fabien LE LEZ
On Sun, 15 Jun 2008 06:29:08 -0700 (PDT), James Kanze
:

Je ne sais pas. J'avoue que les règles ici sont trop compliquées
pour moi.


OK. Dans ce cas, on peut quasiment dire que c'est un comportement
indéfini : stricto sensu, le code a peut-être un sens, mais (presque)
personne ne sait lequel ;-)

Avatar
Fabien LE LEZ
On Sun, 15 Jun 2008 10:47:11 +0200, "Patrick 'Zener' Brunet" :

Je crois que toute la subtilité se trouve en effet dans cette définition de
"l'état observable", à ne pas confondre avec l'implémentation technique.
[...]
Evidemment le cache est déclaré mutable car techniquement on le fait.


Écrire qu'une fonction est "const" est à considérer avant tout comme
de la documentation : l'auteur de la classe s'engage à ce que l'objet
paraisse inchangé après l'appel. Il s'agit là d'un contrat entre deux
êtres humains, avec l'espoir qu'ils soient d'accord sur la notion
d'"inchangé". Le compilateur, lui, tente d'aider le programmeur, en
l'avertissant s'il repère un truc bizarre, mais avec une notion assez
restrictive du mot "inchangé". Si le compilateur se trompe, on peut le
lui indiquer en utilisant "const_cast<>".

Et finalement, déclarer un membre "mutable" revient à dire au compilo
que this->membre doit être remplacé par
"const_cast<C*>(this)->membre".

Avatar
Mickaël Wolff

Écrire qu'une fonction est "const" est à considérer avant tout comme
de la documentation


Je ne suis pas trop d'accord. C'est tout de même plus que ça, puisque
dans le cas où tu décide de déclarer :

void toto() const ;
et
void toto() ;

Tu obtiens deux fonctions aux signatures différentes, qui peuvent être
de contenu différent (et donc avoir un comportement différent). La seule
différence vient du fait que la première est appelée sur un objet const,
et le second sur un non-const ; ainsi que la première ne pouvant
modifier d'autres membres que des membres mutables.

Pour moi, c'est bien plus qu'un attribut de documentation.
--
Mickaël Wolff aka Lupus Michaelis
http://lupusmic.org

Avatar
Fabien LE LEZ
On Mon, 16 Jun 2008 11:51:16 +0200, Micka&euml;l Wolff<br />
&lt;&gt;:<br />
<br />
<blockquote class="block0"><br />
ainsi que la premi&egrave;re ne pouvant<br />
modifier d'autres membres que des membres mutables.<br />
<br />
</blockquote><br />
La premi&egrave;re s'engage &agrave; ne modifier que des membres mutables, mais peut<br />
en fait modifier n'importe quel membre.<br />
<br />
Avatar
James Kanze
On Jun 16, 11:51 am, Micka&euml;l Wolff &lt;&gt; wrote:<br />
<br />
<blockquote class="block0"><br />
<blockquote class="block1"><br />
&Eacute;crire qu'une fonction est &quot;const&quot; est &agrave; consid&eacute;rer avant<br />
tout comme de la documentation<br />
<br />
</blockquote><br />
Je ne suis pas trop d'accord. C'est tout de m&ecirc;me plus que &ccedil;a,<br />
puisque dans le cas o&ugrave; tu d&eacute;cide de d&eacute;clarer :<br />
<br />
void toto() const ;<br />
et<br />
void toto() ;<br />
<br />
Tu obtiens deux fonctions aux signatures diff&eacute;rentes, qui<br />
peuvent &ecirc;tre de contenu diff&eacute;rent (et donc avoir un<br />
comportement diff&eacute;rent). La seule diff&eacute;rence vient du fait que<br />
la premi&egrave;re est appel&eacute;e sur un objet const, et le second sur<br />
un non-const ; ainsi que la premi&egrave;re ne pouvant modifier<br />
d'autres membres que des membres mutables.<br />
<br />
Pour moi, c'est bien plus qu'un attribut de documentation.<br />
<br />
</blockquote><br />
Je crois que ce que Fabien essayait &agrave; dire, c'est qu'en g&eacute;n&eacute;ral,<br />
on utilise un const logique, et non un const bit-&agrave;-bit. Donc, si<br />
j'ai une classe :<br />
class String<br />
{<br />
char* text ;<br />
// ...<br />
} ;<br />
d&eacute;clarer la fonction const, c'est une fa&ccedil;on de documenter qu'on<br />
ne change pas le contenu de text non plus, m&ecirc;me si du point de<br />
vue du langage, c'est tout &agrave; fait permis.<br />
<br />
--<br />
James Kanze (GABI Software) email:<br />
Conseils en informatique orient&eacute;e objet/<br />
Beratung in objektorientierter Datenverarbeitung<br />
9 place S&eacute;mard, 78210 St.-Cyr-l'&Eacute;cole, France, +33 (0)1 30 23 00 34<br />
<br />
<br />
Avatar
Patrick 'Zener' Brunet
Bonsoir.<br />
<br />
&quot;James Kanze&quot; &lt;&gt; a &eacute;crit dans le message de news:<br />
<br />
<blockquote class="block0"><br />
On Jun 15, 8:59 pm, &quot;Patrick 'Zener' Brunet&quot;<br />
&lt;&gt; wrote:<br />
<blockquote class="block1"><br />
Mais comme tu le pr&eacute;cises:<br />
<br />
[...]<br />
mais pas (dans une classe):<br />
<br />
int * mutable mpi; // passerait comme attendu<br />
int mutable * pmi; // l&agrave; c'est r&acirc;t&eacute;...<br />
<br />
</blockquote><br />
Au contraire, c'est le premier qui ne passe pas.<br />
Le &laquo; mutable &raquo; peut prendre les m&ecirc;mes positions que<br />
&laquo; static &raquo; ou &laquo; extern &raquo; dans une declaration, c-&agrave;-d<br />
n'importe o&ugrave; dans la &laquo; declaration-specifier-seq &raquo;,<br />
mais seulement dans elle.<br />
<br />
</blockquote><br />
Alors j'ai bien fait d'aller au fond des choses, parce que<br />
moi j'en ai un qui passe depuis longtemps, au moins sur<br />
MS Visual C++ v.6:<br />
<br />
template&lt; class T&gt;<br />
class CmHolderByValue<br />
{<br />
private:<br />
T mutable _xValue;<br />
public:<br />
...<br />
};<br />
<br />
Que je valide sans probl&egrave;me avec T = int * ou void *.<br />
<br />
Et cette classe est tr&egrave;s largement utilis&eacute;e, avec succ&egrave;s.<br />
De plus je force le test par instanciation de toutes les m&eacute;thodes de chacun<br />
de mes templates avec tous les types d'arguments, en instance const et non<br />
const. Aucune erreur.<br />
<br />
A moins bien s&ucirc;r que l'instanciation du template ci-dessus remette les<br />
choses en ordre: le compilo r&eacute;ordonnerait en &quot;mutable T&quot; avant d'instancier<br />
?<br />
C'est vrai que je n'ai pas d'exemple de pointeur mutable dans mes classes<br />
ordinaires.<br />
<br />
Bon, j'ai fait un grep, je n'en ai qu'une dizaine &agrave; corriger, mais &ccedil;a me<br />
casse un peu la barraque.<br />
<br />
<blockquote class="block0"><br />
La notation &laquo; &agrave; droite &raquo; ne concerne en fait que les<br />
&laquo; cv-qualifier &raquo;, c-&agrave;-d const et volatile. Parce que<br />
ce sont les seuls qui peuvent servir dans le<br />
&laquo; declarator &raquo;, les seuls qui interviennent sur le type,<br />
et les seuls qui peuvent s'appliquer &agrave; plusieurs niveaux<br />
(soit au pointeur, soit au point&eacute;).<br />
<br />
</blockquote><br />
Bien vu... C'est bien une classe de stockage et pas un modifieur, m&ecirc;me si &ccedil;a<br />
cr&eacute;e pour un membre une exception au const qui s'appliquerait &agrave; l'instance<br />
de la class contenante...<br />
<br />
Si on pense &agrave; se souvenir :-) de ce d&eacute;tail, puisque le compilo laisse<br />
passer, la notation &agrave; droite reste valide pour les autres.<br />
<br />
Je vais int&eacute;grer cette subtilit&eacute; dans mes guidelines.<br />
<br />
Merci++<br />
--<br />
Cordialement.<br />
--<br />
* Patrick BRUNET www.ipzb.fr<br />
* E-mail: lien sur http://zener131.free.fr/ContactMe<br />
<br />
<br />
1 2 3