Bon usage du qualificateur "const" pour fonctions membres

Le
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
//
};
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses Page 1 / 3
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Fabien LE LEZ
Le #7001311
On Fri, 13 Jun 2008 15:43:49 -0700 (PDT), bylybob
Alors "bool calcule();" ou "bool calcule() const;"?


D'une manière générale, si tu peux mettre "const" sans que le
compilateur ne râle, alors mets-le.
La question se pose parfois quand rajouter "const" oblige à utiliser
"mutable", mais ce n'est pas le cas ici.

class calculateur {
public:
calculateur(maillage* m,
bool calcule() const { // Const ou pas const?
m->stocke(resultat);
}


Ici, la situation est claire : "calculateur" est une classe à qui
l'utilisateur donne l'autorisation de modifier le paramètre passé au
constructeur. L'objet de type "maillage" n'appartient pas à
"calculateur" ; le membre de "calculateur" est un pointeur passé par
le code utilisateur. Ainsi, sémantiquement parlant, une modification
de *m n'implique pas une modification de l'objet "calculateur".

Fabien LE LEZ
Le #7001301

D'une manière générale, si tu peux mettre "const" sans que le
compilateur ne râle, alors mets-le.


Et tant qu'à faire, un contre-exemple :

class C
{
public:
C() { n= new int (42); }
void f() { ++ *n; }
int GetValeur() const { return *n; }
private:
int *n;
};




Ici, C::f() pourrait être const sans que le compilateur ne râle, mais
ce serait sémantiquement incorrect : n est un membre à part entière de
C, et modifier *n revient à modifier le C. On peut dire aussi que
appeler f() modifie l'état observable du C : en particulier, le
résultat de GetValeur() est différent avant et après l'appel à f().

bylybob
Le #7001391
On Jun 13, 4:23 pm, Fabien LE LEZ
On Fri, 13 Jun 2008 15:43:49 -0700 (PDT), bylybob
Alors "bool calcule();" ou "bool calcule() const;"?


D'une manière générale, si tu peux mettre "const" sans que le
compilateur ne râle, alors mets-le.
La question se pose parfois quand rajouter "const" oblige à utiliser
"mutable", mais ce n'est pas le cas ici.

class calculateur {
public:
calculateur(maillage* m,
bool calcule() const { // Const ou pas const?
m->stocke(resultat);
}


Ici, la situation est claire : "calculateur" est une classe à qui
l'utilisateur donne l'autorisation de modifier le paramètre passé au
constructeur. L'objet de type "maillage" n'appartient pas à
"calculateur" ; le membre de "calculateur" est un pointeur passé par
le code utilisateur. Ainsi, sémantiquement parlant, une modification
de *m n'implique pas une modification de l'objet "calculateur".


Effectivement, c'est plus une question de nature semantique que de
nature technique. Pose ainsi, cela me semble clair que le "const" se
justifie.

Mon doute venait du probleme d'interface suivant:


class calculateur_abstrait {

public:
maillage* get_maillage();

const maillage* get_maillage() const;

bool calcule() const {
// Check pre-condition(s)
bool ok = do_calcule();
// Check post-condition(s)
}

protected:
calculateur_abstrait(maillage* m,
virtual bool do_calcule() const = 0;
};

class calculateur_concret : public calculateur_abstrait {

public:
calculateur_concret(maillage* m,
virtual bool do_calcule() const {
// Calcule des trucs...
get_maillage()->stocke(resultat); // Pas possible, appel
"get_maillage() const" renvoie un "const maillage*"
//...
}
};

Comment resoudriez vous le probleme?


Michel Decima
Le #7001981
On Jun 13, 4:23 pm, Fabien LE LEZ
On Fri, 13 Jun 2008 15:43:49 -0700 (PDT), bylybob


Mon doute venait du probleme d'interface suivant:


class calculateur_abstrait {

public:
maillage* get_maillage();

const maillage* get_maillage() const;

bool calcule() const {
// Check pre-condition(s)
bool ok = do_calcule();
// Check post-condition(s)
}

protected:
calculateur_abstrait(maillage* m,
virtual bool do_calcule() const = 0;
};

class calculateur_concret : public calculateur_abstrait {

public:
calculateur_concret(maillage* m,
virtual bool do_calcule() const {
// Calcule des trucs...
get_maillage()->stocke(resultat); // Pas possible, appel
"get_maillage() const" renvoie un "const maillage*"
//...
}
};

Comment resoudriez vous le probleme?



Une seule fonction get_maillage dans calculateur_abstrait, comme ceci:

maillage* get_maillage() const;


James Kanze
Le #7006311
On Jun 14, 12:43 am, 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;"?

class calculateur {
public:
calculateur(maillage* m,
bool calcule() const { // Const ou pas const?
//- Calcule des trucs...
m->stocke(resultat);
//...
}

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


C'est un problème sans bonne réponse. Ici, dans ton exemple
simple, ça ne fait pas beaucoup de différence. Mais typiquement,
ce genre de classes sont des classes abstraites de base. Où on a
une fonction quelque part (peut-être dans maillage) du genre :
Maillage::calcule( Calculateur /* const? */ & calc ) ;
(Évidemment, le const correspond à celui sur la fonction
virtuelle pûre dans la classe.) Or, si on met le const, on
empêche à jamais qu'un client puisse utiliser un Calculateur qui
a un état interne. Et si on ne le met pas, on empêche à jamais
qu'un client puisse nous passer un temporaire. (C'est peut-être
ce dilemme qui a amené la STL à passer des objets fonctionnels
par valeur, plutôt que par référence.)

--
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

Fabien LE LEZ
Le #7006591
On Sat, 14 Jun 2008 13:21:47 -0700 (PDT), James Kanze

Maillage::calcule( Calculateur /* const? */ & calc ) ;
[...] (C'est peut-être
ce dilemme qui a amené la STL à passer des objets fonctionnels
par valeur, plutôt que par référence.)


Il me semble qu'on est assez loin du problème initial.
Ton Maillage::calcule, de même que for_each ou accumulate, peut très
bien appeler une fonction membre de Calculateur plusieurs fois. Et
effectivement, avoir un état peut s'avérer utile.

La question de l'op était de savoir si Calculateur::calcule() devait
être const ou pas. Et cette question a une réponse exacte : soit
calcule() modifie l'état observable de l'objet Calculateur, soit elle
ne la modifie pas.

Fabien LE LEZ
Le #7006751
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 ?

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);
}

Patrick 'Zener' Brunet
Le #7008201
Bonjour.

"Fabien LE LEZ"
On Sat, 14 Jun 2008 13:21:47 -0700 (PDT), James Kanze
[...]
La question de l'op était de savoir si Calculateur::calcule()
devait être const ou pas. Et cette question a une réponse exacte:
soit calcule() modifie l'état observable de l'objet Calculateur,
soit elle ne la modifie pas.



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.

Par exemple:
J'ai eu le cas avec une classe Date à capacité très étendue et dotée d'une
multitude de méthodes.
La détermination du caractère bissextile d'une année est coûteux, et donc
mérite de n'être fait que lors du premier besoin éventuel, puis mis en cache
au cas où.
Ce qui n'empêche pas de considérer qu'une Date peut être constante et donc
de déclarer cette méthode comme const: sémantiquement on ne modifie pas la
Date en demandant .IsLeapYear().
Evidemment le cache est déclaré mutable car techniquement on le fait.


[demi-HS]

A ce propos, questions subsidiaires aux experts:

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 ?
Pourtant je l'utilise exclusivement dans ce sens et avec succès...

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.
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;

Y a-t-il des compilos modernes que ça peut gêner ?
Merci de votre expertise.

[/demi-HS]

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

Michel Decima
Le #7008781
A ce propos, questions subsidiaires aux experts:

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 ?
Pourtant je l'utilise exclusivement dans ce sens et avec succès...

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.
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;

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


J'utilise aussi la notation "a droite", por les memes raisons que toi.
Le seul inconvenient que j'ai rencontré jusqu'a present, c'est que
gcc genere ses messages en retablissant l'ordre "a gauche", comme
par exemple:

void foo( int const* ) ;
void foo( void const* ) ;
void bar() { foo( 0 ) ; }

foobar.cc: In function 'void bar()':
foobar.cc:3: error: call of overloaded 'foo(int)' is ambiguous
foobar.cc:1: note: candidates are: void foo(const int*)
foobar.cc:2: note: void foo(const void*)

D'un autre coté, ca montre bien que pour lui, c'est bonnet blanc et
blanc bonnet.

James Kanze
Le #7009911
On Jun 14, 11:22 pm, Fabien LE LEZ
On Sat, 14 Jun 2008 13:21:47 -0700 (PDT), James Kanze

Maillage::calcule( Calculateur /* const? */ & calc ) ;
[...] (C'est peut-être
ce dilemme qui a amené la STL à passer des objets fonctionnels
par valeur, plutôt que par référence.)


Il me semble qu'on est assez loin du problème initial. Ton
Maillage::calcule, de même que for_each ou accumulate, peut
très bien appeler une fonction membre de Calculateur plusieurs
fois. Et effectivement, avoir un état peut s'avérer utile.

La question de l'op était de savoir si Calculateur::calcule()
devait être const ou pas. Et cette question a une réponse
exacte : soit calcule() modifie l'état observable de l'objet
Calculateur, soit elle ne la modifie pas.


Sauf que comme j'ai dit dans la partie que tu as coupée, si
Calculateur::calcule() est const ou non est intimement lié à si
Maillage::calcule prend une référence const ou non. Si
Maillage::calcule prend une référence const, il ne peut pas
appeler une fonction non-const de Calculateur ; il faut bien que
Calculateur::calcule() soit const.

Comme j'ai dit, c'est un problème sans véritable solution (en
supposant, évidemment, qu'on ne sait pas ce que le client
pourrait vouloir faire dans Calculateur::calcule()). (La
solution de la STL, de se servir de passe par valeur, plutôt que
passe par référence, n'en est pas une. Elle résoud bien la
question si Calculateur::calcule() doit être const, puisqu'elle
marche dans les deux cas. Mais du fait qu'on travaille sur une
copie pose des problèmes par rapport à un état.)

--
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


Publicité
Poster une réponse
Anonyme