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

3 4 5 6 7
Avatar
loufoque

Mais enfin, je parle là pour un développer lambda. Ce qui s'est
passé avec << et >>, que la plupart des programmeurs C++ les
considèrent des opérateurs d'insertion et d'extraction


N'importe qui sait qu'il s'agit des opérateurs left shift et right shift
simplement surchargés pour quand il y a un stream à gauche.
Un programmeur C++ est au courant du principe selon lequel un opérateur
a /a priori/ un comportement totalement différent en fonction des types
sur lequel on l'applique.

a + b est équivalent à operator+(a, b) dans le cas où a ou b est un objet.

La bibliothèque standard a effectivement fait le choix de s'en servir
pour l'insertion et l'extraction mais c'est une simple convention (qu'il
est conseillé de respecter si on veut travailler avec la bibliothèque
standard)


Dans
la contexte de la STL, dans la mesure qu'elle est devenu une
partie de la norme, et en fait une partie integrante du C++, on
a aussi défini une convention et des expectations.


Avant 1998, le C++ n'était pas standardisé. Dès qu'il l'a été, ce qu'on
appelait à l'époque la STL (car C++ existait avant qu'il ne soit
clairement défini) a été intégré dans sa bibliothèque standard.


Mais je ne
veux pas que le programmeur lambda prend ces exemples comme
excuse pour surcharger n'importe quoi n'importe comment.


Le programmeur est libre de faire ce qu'il veut avec les types qu'il
invente quand même, a lui d'en juger la pertinence.
Dire "la surchage c'est mal" ne mène à rien.

Et ce n'est pas vraiment des exemples, mais des outils généraux fournis
par l'implémentation.


on tombe très vite dans l'obfuscation.


Le problème étant qu'il faut se réferer à la définition de
operator+(TypeDeGauche, TypeDeDroite) pour savoir ce que ça fait, alors
qu'une fonction a généralement un nom plus explicite.

Enfin une fois qu'on est familiarisé avec l'objet en question ça ne
devrait plus poser de problèmes.


Et le fait que c'est devenu la norme
« rédéfinit » les conventions du langage


En fait ça définit et non pas redéfinit, mais ça définit les conventions
standard du langage.

Libre à n'importe qui de recréer autre chose avec les outils
d'abstraction du C++ pour une utilisation particulière et de définir
alors de nouvelles conventions, comme le fait Microsoft (qui n'a
d'ailleurs fourni un compilateur C++ potable qu'en 2003, on voit à quel
point le C++ standard l'intéresse)

Avatar
loufoque

Maintenant, si tu veux parler des cas où le nom ne convient
vraiment pas, uniquement sur la base de sa sémantique, parlons
de std::remove. (Et en passant, pourquoi dans la STL, il
s'appelle parfois remove, et d'autres fois erase ?)


Extrait en anglais :

The meaning of "removal" is somewhat subtle. Remove does not destroy any
iterators, and does not change the distance between first and last.
(There's no way that it could do anything of the sort.) So, for example,
if V is a vector, remove(V.begin(), V.end(), 0) does not change
V.size(): V will contain just as many elements as it did before. Remove
returns an iterator that points to the end of the resulting range after
elements have been removed from it; it follows that the elements after
that iterator are of no interest, and may be discarded. If you are
removing elements from a Sequence, you may simply erase them. That is, a
reasonable way of removing elements from a Sequence is
S.erase(remove(S.begin(), S.end(), x), S.end()).

Avatar
kanze
Fabien LE LEZ wrote:
On 20 Jan 2006 01:22:38 -0800, "kanze" :

Dans le cas de la STL, j'estime qu'il y avait bien un peu d'abus


Pourquoi ?

La règle dans la surcharge des opérateurs, c'est qu'ils
fassent la même chose que les opérateurs sur un type de base.
Généralement, on prend int comme "base", mais étant donné que
le(s) type(s) "iterator" de la STL sont faits pour généraliser
la notion de pointeur, avoir la même sémantique que les
pointeurs ne me paraît pas un abus.

On peut, par contre, estimer que le mot "iterator" est ici un
abus, mais ce n'est plus une histoire de surcharge.


Vue comme ça, je suis d'accord.

Le problème n'est même pas l'utilisation du mot « iterator » en
soi. C'est son utilisation dans la contexte du C++, où il y
avait un concepte assez établi d'itérateur avant. Pas toujours
très précis, je l'avoue, mais qui regroupait un certain nombre
d'aspects communs quand même. Alors, ça prète à confusion.

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

Mais enfin, je parle là pour un développer lambda. Ce qui
s'est passé avec << et >>, que la plupart des programmeurs
C++ les considèrent des opérateurs d'insertion et
d'extraction


N'importe qui sait qu'il s'agit des opérateurs left shift et
right shift simplement surchargés pour quand il y a un stream
à gauche.


C'était vrai il y a un certain temps. Mais il ne faut pas
oublier qu'il y a beaucoup de programmeurs qui ne savent même
pas ce que c'est un déclage, et encore moins qui s'en servent
dans leur vie professionnelle. Quand on a travaillé à bas niveau
en C pendant quelque temps, évidemment, on le sait. Mais même
alors, ça appartient à l'histoire -- en C++, << est surtout
l'opérateur d'insérsion, « abusivement » surchargé (pour des
raisons historiques et de compatibilité avec C) pour faire un
décalage à gauche quand l'opérand à gauche a un type entier.

Un programmeur C++ est au courant du principe selon lequel un
opérateur a /a priori/ un comportement totalement différent en
fonction des types sur lequel on l'applique.


Un programmeur C++ qui donne à un opérateur un comportement
totalement différent ne durera pas longtemps dans une boîte où
je travaille. La lisabilité du code est importante.

[...]
Le programmeur est libre de faire ce qu'il veut avec les types
qu'il invente quand même, a lui d'en juger la pertinence. Dire
"la surchage c'est mal" ne mène à rien.


Attention : je n'ai pas dit que la surcharge est mal. Au
contraire, je l'estime essentiel dans certains cas -- je
n'aimerais pas à avoir à écrire :
prixTTC = prixHT.add( prixHT.mul( 0.196 ) ) ;
simplement parce qu'il faut utiliser un type décimal.

Mais un programmeur professionnel n'est pas libre de faire ce
qu'il veut. Son code doit être facile à comprendre et à
maintenir. Donner des sémantiques arbitraires à des opérateurs
n'y aide pas.

[...]
on tombe très vite dans l'obfuscation.


Le problème étant qu'il faut se réferer à la définition de
operator+(TypeDeGauche, TypeDeDroite) pour savoir ce que ça
fait, alors qu'une fonction a généralement un nom plus
explicite.


Le problème, c'est que l'opérateur lui aussi a un nom très
explicit. On sait bien ce que c'est l'addition en C++ ; c'est
aussi défini que le mot « add ».

Enfin une fois qu'on est familiarisé avec l'objet en question
ça ne devrait plus poser de problèmes.


Tu n'auras donc pas d'objection que ma classe Decimal utilise
l'opérateur - pour l'addition, et l'opérateur + pour la
soustraction ?

C'est le genre d'attitude qui a donné une mauvaise réputation au
surcharge.

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

| Fabien LE LEZ wrote:
| > On 20 Jan 2006 01:22:38 -0800, "kanze" :
|
| > >Dans le cas de la STL, j'estime qu'il y avait bien un peu d'abus
|
| > Pourquoi ?
|
| > La règle dans la surcharge des opérateurs, c'est qu'ils
| > fassent la même chose que les opérateurs sur un type de base.
| > Généralement, on prend int comme "base", mais étant donné que
| > le(s) type(s) "iterator" de la STL sont faits pour généraliser
| > la notion de pointeur, avoir la même sémantique que les
| > pointeurs ne me paraît pas un abus.
|
| > On peut, par contre, estimer que le mot "iterator" est ici un
| > abus, mais ce n'est plus une histoire de surcharge.

Je suggère de lire Stepanov pour avoir une idée de comment on en est
arrivé.

http://www.stepanovpapers.com/

ou lui envoyer un mail -- il répond à ses couriers (l'excuse, il a
quelque chose de plus important à faire est irrecevable.)

| Vue comme ça, je suis d'accord.
|
| Le problème n'est même pas l'utilisation du mot « iterator » en
| soi. C'est son utilisation dans la contexte du C++, où il y
| avait un concepte assez établi d'itérateur avant.

Mais quel est-il ?

| Pas toujours très précis, je l'avoue, mais qui regroupait un certain nombre
| d'aspects communs quand même.

Comme ?

-- Gaby
Avatar
Gabriel Dos Reis
"kanze" writes:

[...]

| > Un programmeur C++ est au courant du principe selon lequel un
| > opérateur a /a priori/ un comportement totalement différent en
| > fonction des types sur lequel on l'applique.
|
| Un programmeur C++ qui donne à un opérateur un comportement
| totalement différent ne durera pas longtemps dans une boîte où
| je travaille.

C'est pour cela qu'Alex ou Bjarne ne travaille pas dans la même boîte
que toi ?

| La lisabilité du code est importante.

[...]

| Le problème, c'est que l'opérateur lui aussi a un nom très
| explicit. On sait bien ce que c'est l'addition en C++ ;

Je suppose que « on » n'est pas moi, alors j'ose : qu'est-ce que c'est ?

| c'est aussi défini que le mot « add ».

Je suppose que c'est pour cela qu'on utilise ou qu'on n'utilise pas

std::string operator+(const std::string&, const std::string&);

-- Gaby
Avatar
kanze
Fabien LE LEZ wrote:
On 20 Jan 2006 05:58:32 -0800, "kanze" :

Quelqu'un a posté une classe avec « iterator » dans son nom,
et certains se sont mis à la critiquer parce qu'elle n'était
pas conforme à la STL.


Ben oui. Quelles que soient les erreurs commises par le
comité, si tu crées une classe qui a exactement le même nom
qu'une classe de la STL, le lecteur va s'attendre à ce qu'elle
soit compatible. Si ce n'est pas le cas, je conseille
fortement de choisir un autre nom -- ne serait-ce que
"Iterator".


Bonne remarque. Dans mon code, c'est à peu près ce qui se passe,
parce que pour des raisons historiques, la convention de nommage
que j'utilise fait commencer les noms de types par une
majuscule.

Dans de nouveau code, il ne me viendrait pas non plus à l'esprit
de ne pas fournir une interface STL à mes itérateurs. En plus de
mon interface classique.

Le problème se présente quand on travaille sur un code
existant ; il faut bien respecter les conventions de ce code, et
en ce qui concerne le comportement des itérateurs, et en ce qui
concerne le nommage.

De même, si tu veux implémenter un vecteur (au sens
mathématique du terme), appeler ta classe "vector" ne fera
qu'apporter de la confusion.


Tout à fait. Et que faire si tu hérites un logiciel qui utilise
ce nom dans son sens mathématique ?

En fait, j'ai une convention simple dans mon code : si un nom
de type ou de fonction ne commence pas par une majuscule,
c'est qu'il a la même sémantique qu'un type (ou une fonction)
homonyme dans la SL.


Moi aussi. Mais ça ne marche que parce que j'ai la convention
depuis longtemps que les noms de types commencent par une
majuscule. (En fait, mes itérateurs mixtes s'appelle en général
Iterator -- quand ce n'est pas carrément Container_Iterator,
parce que le compilateur que j'utilisais à l'époque avait du mal
avec des classes embriquées dans un template -- et il y a un
typedef pour iterator. Du fait qu'ils implémentent deux idiomes,
ils ont deux noms.)

Maintenant, si tu veux parler des cas où le nom ne convient
vraiment pas, uniquement sur la base de sa sémantique,
parlons de std::remove.


On va dire que le "re" est de trop.


Sauf que « move » ne serait pas assez précis non plus:-).

Pratiquement, je crois qu'on ne peut pas enlever quelque chose
d'une suite définit par des itérateurs -- c'est le cas des
itérateurs STL, mais c'est aussi le cas de tous les itérateurs
classiques que j'ai connus. On enlève de la collection
sous-jacente (et quand la suite n'est pas définie par une
collection, on n'enlève pas). Je ne sais pas si c'est une bonne
restriction, mais c'est un fait dans la pratique.

(Et en passant, pourquoi dans la STL, il s'appelle parfois
remove, et d'autres fois erase ?)


Il y a des cas où "erase" n'efface pas des éléments ?


Au contraire, je crois. Erase enlève (« efface ») toujours
l'élément. Remove seulement si elle est fonction membre d'une
collection. Dans la STL, remove s'utilise quand l'effacement
dépend de la valeur de l'élément, ou d'un prédicat ; erase quand
on précise les éléments à enlever selon leur position dans la
collection. Sauf que quand la valeur détermine la position (les
collectons « associatives » -- encore un nom mal-choisi), c'est
toujours eraise.

Ce n'est pas un grand problème dans la mesure où personne
n'oublie que le mot peut avoir plusieurs significations, et
où tout le monde le tient en compte, et se pose la question
d'abord, quelle est la signification voulue par auteur ici,


Cf "méthode"...


:-) Très bon exemple.

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

Maintenant, si tu veux parler des cas où le nom ne convient
vraiment pas, uniquement sur la base de sa sémantique,
parlons de std::remove. (Et en passant, pourquoi dans la
STL, il s'appelle parfois remove, et d'autres fois erase ?)


Extrait en anglais :


De quoi ?

The meaning of "removal" is somewhat subtle.


J'aime le mot « subtle » ici. Ce qu'il veut dire, c'est que la
signification de « removal » n'a rien à voir avec sa
signification habituelle en anglais.

Remove does not destroy any iterators, and does not change the
distance between first and last. (There's no way that it could
do anything of the sort.)


Je crois que la parenthèse, c'est la clé. Ce qu'on vise, c'est
bien d'enlever les éléments, mais on ne peut pas. Quand on peut
(par exemple list::remove), on le fait.

N'empèche que c'est bien un cas où la fonction ne fait pas ce
que dit son nom. En fait, il aurait été plus logique (du point
de vue linguistique) ou que la fonction prenent une collection
en paramètre, ou qu'elle soit membre, et qu'on ne l'applique pas
aux suites arbitraires (où l'idiome des itérateurs fait qu'on ne
peut pas enlever quelque chose sans avoir accès à la collection
sousjacente).

Seulement, dans la contexte de la STL, il y a de bonnes raisons
pour préférer la fonction libre sur des itérateurs. C'est
simplement dommange qu'on n'en a pas pu trouver un meilleur nom.
(Typiquement, le fait de ne pas pouvoir trouver un nom qui
convient est symptomatique d'une erreur de conception. Dans ce
cas précis, je n'en suis pas vraiment convaincu.)

--
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
John Deuf wrote:
kanze :

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.


Alors qu'est-ce que tu proposerais pour remplacer les
operateurs << et >> sur les stream ?


Un fonction avec un nom, du genre :
std::cout.write( "i = " ).write( i ).write( std::endl ) ;
sinon l'operator() :
std::cout( "i = " )( i )( std::endl ) ;
(mais je préfère le premier).

Évidemment, ces options n'étaient pas disponibles quand iostream
a été conçu. Pratiquement, il leur fallait un opérateur s'il
voulait permettre à l'utilisateur de fournir des surcharges, et
aussi offrir une écriture autre que :
write( write( write( std::cout, "i = " ), i ), std::endl ) ;

Le choix de << et de >> prend en compte plusieurs
considérations : la précédance, le poids visuels, mais aussi, je
crois, le fait que ces opérateurs étaient peu connus, ou au
moins peu utilisés par des programmeurs moyens. De ce point de
vue, ce n'est pas tellement un surcharge que la création d'un
nouvel opérateur.

C'est un privilège que personne n'a plus.

Parce que dans ce contexte, il me semble qu'ils n'ont aucun
rapport avec le decalage de bit. (Et je ne trouve pas qu'ils
rendent le programme illisible.)


C'est sur que le contexte est tout. Les décalages ne sont pas
les opérateurs qu'on utilisent beaucoup. Et le surcharge existe
depuis les tous premiers débuts du C++. C'est donc en fait qu'on
ne le ressent pas comme un surcharge abusif -- s'il y a un abus,
c'est l'utilisation de ces opérateurs pour les décalages.

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

| John Deuf wrote:
| > kanze :
|
| > > 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.
|
| > Alors qu'est-ce que tu proposerais pour remplacer les
| > operateurs << et >> sur les stream ?
|
| Un fonction avec un nom, du genre :
| std::cout.write( "i = " ).write( i ).write( std::endl ) ;
| sinon l'operator() :
| std::cout( "i = " )( i )( std::endl ) ;
| (mais je préfère le premier).

Je suis persuadé que quelqu'un serait venu développer un passage de
l'Apocalypse sur la surcharge d'opérateur ().

-- Gaby
3 4 5 6 7