OVH Cloud OVH Cloud

Sens de const

64 réponses
Avatar
Vincent Lascaux
Bonjour,

Je voudrais savoir quel sens vous mettez derriere le mot clé "const", ou
plutot quel contrat une fonction qui a des parametres const doit remplir.

Pour être plus clair, voici le cas qui m'interesse plus particulierement :
nous avons une classe "iterator" qui me permet d'itérer sur une structure de
donnée. Un "iterator" a de bonnes raisons (qui ne sont pas discutables :))
d'être un peu lourd à copier. Maintenant imaginons une fonction
"nbElementsAfter" qui fait ca :

int nbElementsAfter(iterator& it)
{
int nb = 0;
while(it.next()) nb++;
for(int i=0; i<nb; i++) it.prev();
return nb;
}

Est-ce que cette fonction doit prendre un iterator& ou un const iterator& ?
Lequel de ces contrats vous semble le plus sensé pour une fonction qui prend
un const iterator& : "je ne modifierai pas l'iterateur" ou "l'iterateur
pointera au même endroit avant et apres l'appel" (ou encore autre chose ?) ?

Merci

--
Vincent

10 réponses

1 2 3 4 5
Avatar
Fabien LE LEZ
On 9 Jan 2006 01:30:06 -0800, "kanze" :

Au fait, si tu appelles ton objet "iterator", il vaudrait
mieux lui mettre des opérateurs ++ et -- au lieu de next() et
prev().


Ça se discute. L'utilisation de next() et de prev() est bien
plus compréhensible.


Après être tombé sur le même type de cas il y a quelques minutes, je
me permets de te contredire.

J'ai une classe "itérateur", avec un opérateur ++ pour avancer, et une
fonction membre "GetNext() const" qui renvoie l'itérateur suivant.

L'opérateur ++, on sait qu'il modifie l'objet -- donc, à la lecture du
code utilisateur, on sait au premier coup d'oeil qu'il y a
modification.

Une fonction "Get...()" ne modifie pas l'objet -- typiquement, une
telle fonction est toujours const. Idem, à la lecture du code
utilisateur, on sait au premier coup d'oeil qu'il y a modification.

Par contre, en voyant une fonction "next()" tout court, on est
généralement obligé d'aller voir la définition de la classe pour
savoir si elle modifie quelque chose ou pas.


Avatar
loufoque

Pour être plus clair, voici le cas qui m'interesse plus particulierement :
nous avons une classe "iterator" qui me permet d'itérer sur une structure de
donnée. Un "iterator" a de bonnes raisons (qui ne sont pas discutables :))
d'être un peu lourd à copier.


Tu devrais suivre les paradigmes de la bibliothèque standard de C++.
Elle introduit déjà un concept d'itérateurs réutilisables, avec nombre
d'algorithmes et autres outils très pratiques.

Avatar
kanze
Fabien LE LEZ wrote:
On 9 Jan 2006 01:30:06 -0800, "kanze" :

Au fait, si tu appelles ton objet "iterator", il vaudrait
mieux lui mettre des opérateurs ++ et -- au lieu de next()
et prev().


Ça se discute. L'utilisation de next() et de prev() est bien
plus compréhensible.


Après être tombé sur le même type de cas il y a quelques
minutes, je me permets de te contredire.

J'ai une classe "itérateur", avec un opérateur ++ pour
avancer, et une fonction membre "GetNext() const" qui renvoie
l'itérateur suivant.


Ce loin d'être l'idiome standard.

L'opérateur ++, on sait qu'il modifie l'objet -- donc, à la
lecture du code utilisateur, on sait au premier coup d'oeil
qu'il y a modification.


Et alors ? Il ne suffit pas de savoir s'il y a modification ou
non, il faut aussi savoir quel genre de modification.

Une fonction "Get...()" ne modifie pas l'objet -- typiquement,
une telle fonction est toujours const. Idem, à la lecture du
code utilisateur, on sait au premier coup d'oeil qu'il y a
modification.

Par contre, en voyant une fonction "next()" tout court, on est
généralement obligé d'aller voir la définition de la classe
pour savoir si elle modifie quelque chose ou pas.


Ou bien on sait ce que fait la classe dont on se sert, ou bien,
c'est foutu d'avance. L'opérateur ++ de base ajoute 1 à l'objet.
Ça veut dire quoi, au juste, si l'objet n'est pas un type
numérique ?

Aujourd'hui, on a l'habitude que ++ avance aussi un itérateur,
de meme qu'on a l'habitude que << format une valeur en texte.
Mais ce sont des habitudes venues de la bibliothèque standard,
qui ont tous les deux des racines dans un abus du surcharge de
l'opérateur.

En ce qui concerne les itérateurs, évidemment, que ce soit ++ ou
next(), il faut toujours régarder la documentation pour savoir
ce que ça signifie exactement. Comment est défini la suite, par
exemple, et est-ce que l'itérateur utilise un sémantique de
copie (ce qu'il faut pour la STL) ou de référence (qui est
beaucoup plus naturel pour certains types d'utilisation) ?

--
James Kanze GABI Software
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
kanze
loufoque wrote:

Pour être plus clair, voici le cas qui m'interesse plus
particulierement : nous avons une classe "iterator" qui me
permet d'itérer sur une structure de donnée. Un "iterator" a
de bonnes raisons (qui ne sont pas discutables :)) d'être un
peu lourd à copier.


Tu devrais suivre les paradigmes de la bibliothèque standard de C++.


Même quand ils vont à l'encontre des bons principes de
programmation en général ? Tout dans la bibliothèque standard
n'est pas parfait.

Elle introduit déjà un concept d'itérateurs réutilisables,
avec nombre d'algorithmes et autres outils très pratiques.


Le choix du nom « itérateur » pour les « pseudo-pointeurs » de
la STL est malheureux ; le concepte d'itérateur existait bien
avant, pour quelque chose de légèrement différent.

En fait, le problème de vocabulaire n'est pas simple. Jusqu'à
quel point est-ce qu'il faut être différent avant qu'un autre
nom s'impose@? Les itérateurs de la STL jouent la plupart du
temps le même rôle d'un itérateur classique. D'où le nom. En
revanche, ils sont assez différents pour que dans certains cas,
on a besoin des deux -- et donc, des noms différents
s'imposent.

--
James Kanze GABI Software
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
loufoque

Même quand ils vont à l'encontre des bons principes de
programmation en général ? Tout dans la bibliothèque standard
n'est pas parfait.


C'est pourtant son but.
Tout un comité a planché dessus pour faire en sorte qu'elle soit la
mieux possible.
Il est vrai néanmoins que les démarches de standardisation sont assez
lentes donc il se peut que ça ne prend pas encore en compte certains
aspects spécifiques.

Néanmoins les approches retenues sont a priori celles qui apportent les
meilleurs compromis.


Le choix du nom « itérateur » pour les « pseudo-pointeurs »


Les itérateurs ne font que surchager l'opérateur de déréferencement (*)
pour fournir l'élément courant et les opérateurs d'incrémentation pour
passer aux éléments suivants ou précédents (en fonction de s'il s'agit
d'un itérateur bidirectionnel ou non)
Ce ne sont pas des pointeurs, même si les pointeurs sont des genres
d'itérateurs dans le cas des tableaux.

de
la STL est malheureux ; le concepte d'itérateur existait bien
avant, pour quelque chose de légèrement différent.


Encore heureux que ça existait avant. L'itération c'est un des principe
de la programmation impérative.

Et quelle est cette différence dont tu parles ?

Avatar
kanze
loufoque wrote:

Même quand ils vont à l'encontre des bons principes de
programmation en général ? Tout dans la bibliothèque
standard n'est pas parfait.


C'est pourtant son but.


Être parfait ? Je ne crois pas. Je dirais même que le rôle
qu'elle joue et la façon qu'elle est élaborée rend la perfection
impossible. Elle est avant tout un compromis, un essai d'être
utile à tout le monde, sans être parfait pour personne.

Tout un comité a planché dessus pour faire en sorte qu'elle
soit la mieux possible.


Plusieurs choses :

-- Le comité n'a pas cherché la perfection, mais le bon
compromis. Andy Koenig a cité plusieurs fois l'expression de
Voltaire : « Le mieux est l'ennemi du bien » (qui dans la
contexte n'était pas loin de signifier « un tiens vaut deux
tu l'auras »).

-- Le comité essaiait de marcher par consensus. Ce qui veut
dire plaire à tout le monde. Or, si certains veut une
conception bien élégante et « parfaite », pour d'autres, les
aspects de performce ou un simple pragmatisme en ce qui
concerne les facilités d'utilisation ou d'implémentation
primaient. D'où pas mal de compromis (pas forcément
mauvais).

-- Si le comité a innové dans certaines choses, ce n'était
quand même ni le but, ni le cas général. Pour la plupart, il
normalisait ce qu'il y avait, et ce qui était proposé. Il
propositions, autant que je sache, n'étaient que quelques
collections (vector, string) ad hoc -- en tant que «
paradigme », la STL en est infiniment supérieur aux
alternatifs qu'avait le comité, même si elle est loin d'être
parfaite. <locale> est un horreur -- mais je ne connais pas
d'autres propositions pour résoudre le problème.

Il est vrai néanmoins que les démarches de standardisation
sont assez lentes donc il se peut que ça ne prend pas encore
en compte certains aspects spécifiques.


Ce n'est pas seulement le problème de la lenteur. C'est le
problème qu'il faut satisfaire des démandes parfois
contradictoires.

Néanmoins les approches retenues sont a priori celles qui
apportent les meilleurs compromis.


Des compromis, en effet. On compromet la conception pour avoir
une meilleur performance, par exemple.

Note bien que les critères de choix en ce qui concerne une
bibliothèque à prétentions universelles ne sont pas forcément
ceux d'une application, ni même d'une bibliothèque plus
spécialisée. À titre d'exemple : je pourrais très bien savoir
qu'une classe X est suffisamment performante pour mon
application, et qu'il n'y a pas besoin de compromettre sa
conception pour des raisons de performance. En revanche, il est
quasiment sûr qu'il y aurait une application quelque part où les
performances de std::vector seront critiques ; on préfère donc
un comportement indéfini (qui n'est jamais acceptable au niveau
d'une application) qui assure la possibilité d'une performance
optimale à la spécification du comportement que voudrait une
conception plus propre.

Le choix du nom « itérateur » pour les « pseudo-pointeurs »


Les itérateurs ne font que surchager l'opérateur de
déréferencement (*) pour fournir l'élément courant et les
opérateurs d'incrémentation pour passer aux éléments suivants
ou précédents (en fonction de s'il s'agit d'un itérateur
bidirectionnel ou non) Ce ne sont pas des pointeurs, même si
les pointeurs sont des genres d'itérateurs dans le cas des
tableaux.


Ce ne sont pas des itérateurs dans le sens classique non plus.
Le fait qu'il en faut deux rend la composition des fonctions
extrèmement difficile.

En fait, les itérateurs à la STL sont un compromis : ils émulent
les pointeurs simplement parce qu'on tenait à ce que les
pointeurs soient des émulateurs. Et on y tenait au moins en
partie pour des raisons de performance -- avec les pointeurs, on
est à peu près garanti la meilleur performance. Du coup, la
conception s'en trouve coincée : on utilise le surcharge des
opérateurs, même si c'est abusif, parce qu'un pointeur ne peut
pas avoir une fonction membre.

de la STL est malheureux ; le concepte d'itérateur existait
bien avant, pour quelque chose de légèrement différent.


Encore heureux que ça existait avant. L'itération c'est un des
principe de la programmation impérative.

Et quelle est cette différence dont tu parles ?


Le modèle itérateur, dans les modèles de conceptions (« Design
Patterns »), est un objet -- un seul. Ce qui permet la
composition des fonctions, quelque chose qui est aussi important
dans la conception. Dans la mesure où un itérateur n'est ni un
type arithmétique, ni un pointeur, on ne lui surcharge pas
d'opérateurs.

--
James Kanze GABI Software
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
Gabriel Dos Reis
"kanze" writes:

[...]

| -- Si le comité a innové dans certaines choses, ce n'était
| quand même ni le but,

j'en ai assez de cette fiction.

-- Gaby
Avatar
loufoque

Le modèle itérateur, dans les modèles de conceptions (« Design
Patterns »), est un objet -- un seul.


Oui, bah c'est bien un objet aussi, tout itérateur de la STL dérivant de
la classe std::iterator<> (enfin il se peut que ce soit un pointeur
directement en effet)

C'est la distinction entre l'objet à itérer et l'itérateur qui te gêne ?
(référence à "un seul", je vois pas trop ce que tu veux dire)


Dans la mesure où un itérateur n'est ni un
type arithmétique, ni un pointeur, on ne lui surcharge pas
d'opérateurs.


Depuis quand c'est mal de surcharger les opérateurs pour les objets ?
Faire it++ et it.next() c'est la même chose, c'est juste une histoire de
syntaxe.

Avatar
Gabriel Dos Reis
loufoque writes:

[...]

| > Dans la mesure où un itérateur n'est ni un
| > type arithmétique, ni un pointeur, on ne lui surcharge pas
| > d'opérateurs.
|
| Depuis quand c'est mal de surcharger les opérateurs pour les objets ?

les croyances religieuses ne se questionnent pas :-)

-- Gaby
Avatar
kanze
loufoque wrote:

Le modèle itérateur, dans les modèles de conceptions
(« Design Patterns »), est un objet -- un seul.


Oui, bah c'est bien un objet aussi, tout itérateur de la STL
dérivant de la classe std::iterator<> (enfin il se peut que ce
soit un pointeur directement en effet)


Pour itérer, il te faut *deux* objets, non un seul. Ce qui rend
l'utilisation très difficile dans certains cas.

C'est la distinction entre l'objet à itérer et l'itérateur qui
te gêne ? (référence à "un seul", je vois pas trop ce que tu
veux dire)


Que je ne veux qu'un objet, non deux, parce que je veux utiliser
la valeur de rétour d'une fonction comme paramètre d'une autre.

Dans la mesure où un itérateur n'est ni un type
arithmétique, ni un pointeur, on ne lui surcharge pas
d'opérateurs.


Depuis quand c'est mal de surcharger les opérateurs pour les
objets ?


Qui a dit quoique ce soit contre l'idée de surcharger les
opérateurs pour les objets. Ce qui n'est pas bien, c'est de
surcharger avec des significations arbitraires, sans rapport
avec la sémantique de l'opérateur de base. À la longue, ça rend
les programmes illisibles.

Faire it++ et it.next() c'est la même chose, c'est juste une
histoire de syntaxe.


Sauf que le premier a une signification prédéfinie par le
langage. Dans le cas de ++ sur un itérateur, on est à la
limite ; le fait que += n'a pas de sens sur un itérateur fait à
mon avis pencher la balance contre, mais on pourrait en
discuter.

Utiliser * pour accéder à l'élément courant, ce un peu moins
limite que ++, mais encore discutable.

Utiliser == ou != pour isDone(), il n'y a aucun rapport ; c'est
de l'abus pur et simple. Remarque, j'en ai vu pire : dans une
livre, j'ai vu l'auteur conseiller l'opérateur + unaire pour
isDone, pour un itérateur classique. Pour pouvoir écrire quelque
chose comme :

for ( Iter i = ... ; + i ; ++ i ) ...

Au fond, ce qui me gène le plus, c'est l'utilisation du mot
itérateur dans la STL. L'abstraction de la STL s'approche assez
à celle des pointeurs classiques ; à cet égard, on pourrait
arguer que le surcharge des opérateurs se justifie. Mais
itérateur a toujours signifier une abstraction d'un niveau un
peu plus élevé, où on ne parle plus en termes d'incrémenter ou
de déréferencer, mais en termes d'avancer ou d'accéder à la
valeur courante. Et encore moins en termes de comparer -- une
itération est terminée, ou elle ne l'est pas, idépendamment de
tout autre objet. Il y a une incohérence dans le vocabulaire :
un « itérateur » ne supporte pas des opérateurs, parce que les
opérations fondamentales sur des itérateurs ne correspondent pas
aux opérations fondamentales sur les types de base.

Et comme toujours, ce n'est pas toujours un cas noir et blanc.
Il y a bien des cas où l'abus est manifest : le cas de
l'opérateur + unaire pour isDone, ou si tu surchargais '*' pour
faire la soustraction dans une classe numérique. Et il y a aussi
des cas où c'est manifestement bon -- les opérateurs +, -, *, /
et % sur une classe BigInt, par exemple, ou * et -> sur un
pointeur intelligent. Mais il y a aussi beaucoup de cas entre
les deux, où la décision n'est pas claire et nette. Dans ces
cas, 1) des mots en langage naturel ont toujours la possibilité
d'être plus expressif, et 2) une cohérence de vocabulaire
s'impose -- si tu appelles le type quelque chose en Pointer, le
surcharge des opérateurs qui convient à un pointeur s'impose ;
si tu l'appelles itérateur, le surcharge est étonnant, pour le
moindre.

--
James Kanze GABI Software
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


1 2 3 4 5