OVH Cloud OVH Cloud

Pas d'operateur de comparaison par defaut

75 réponses
Avatar
Olivier Croquette
Salut

Le compilateur génére automatiquement certains opérateurs par défaut
pour les classes qui ne le font pas automatiquement.
Sauf erreur de ma part, il s'agit de:
- constructeur par défaut (Classe::Classe())
- constructeur de recopie (Classe::Classe(const Class&))
- affectation (Classe::operator = (const Classe&))

Pourquoi n'est-il pas prévu qu'il fasse de même pour l'opérateur (par
exemple) de comparaison (==)?
L'implémentation naturelle serait de considérer ses attributs un à un,
tout comme pour la recopie.
Y a-t'il une raison?

10 réponses

4 5 6 7 8
Avatar
Loïc Joly
- NaN n'est pas une valeur, donc rappeler que NaN n'est pas unique
était une porte ouverte; par contre ayant un entier évalué comme NaN
(par _isnan) sa copie par memcmp sera également évaluée comme NaN.



Oui, mais un autre intervenant n'a-t-il pas souligné que la
comparaison de deux NaN, même égaux bits à bit, devait être
évaluée comme fausse ?



merci de le citer !!! lui (nous) répondre pourquoi aurait été encore
plus sympa; pour ma part je ne peux pas reproduire son résultat et je ne
l'explique pas; je le tiens, en l'état actuel, pour une erreur de son
compilo .


Mon compilateur (VC++8.0) reproduit très bien ce comportement avec le
code suivant :
#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
double d2 = d1;
cout << boolalpha << (d1=Ò) << endl;
cout << (memcmp(&d1, &d2, sizeof (double)) == 0) << endl;
return 0;
}



Et puis il y a aussi les attributs *mutable*, qu'il va falloir
ignorer.



ah, volatile, mutable, à qui le prochain ??

rappelle-moi, ou corrige moi si besoin, un attribut "mutable" est un
machin qui a le droit de changer de valeur dans un contexte const, non ?


Oui.


cela ne concerne que le compilo et les contrôles qu'il fera sur l'accès
à cette variable - en aucun cas la façon dont elle sera mappée en
mémoire (comme sa non prise en compte dans un sizeof, ou "ailleurs" non
contiguë aux autres données).

quel rapport donc entre ce mutable et le fait que 2 instances ayant tous
leurs champs égaux (bit à bit) seront égales


Jusque là, aucun.

tandis que si elles
différent par la valeur d'un champ, même marqué mutable, elles seront
vues différentes ???


Là, il y a le fait qu'en général, les attributs mutables servent à
définir des informations purement techniques (l'état ou la valeur d'un
cache de calcul), et qu'elles ne font pas partie de l'état observable
d'une instance. Rien n'oblige ça, mais ce serait un très mauvais style
de programmation d'utiliser mutable sans respecter cette condition.

Comme elles ne font pas partie de l'état observable, il serait
intéressant de les sauter lors d'une comparaison membre à membre générée
automatiquement.

--
Loïc



Avatar
Loïc Joly
"==" est univoque.

selon le Littré: "Qui n'est susceptible que d'une seule interprétation".
(par opposition à 'équivoque': "Qui peut s'interpréter en différents
sens..."

"==" est bien univoque, il ne serait être interpréter comme "tantôt ça
compare les opérandes", "tantôt ça les additionne".


"==" n'est pas univoque. Il peut y avoir pléthore de relations
d'équivalence sur des objets. Par exemple, prenons une classe de fraction.

Il est tout à fait légitime et mathématiquement accepté d'écrire un
opérateur == pour une telle classe qui retourne vrai lorqu'il compare
1/2 et 2/4.

Il est tout aussi correct et mathématiquement accepté qu'il retourne
faux pour ces même valeurs.

L'opérateur à implémenter dépend du contexte d'utilisation, de l'état
d'esprit du développeur,... Il n'a rien d'univoque.

--
Loïc

Avatar
Falk Tannhäuser
Loïc Joly schrieb:
#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
double d2 = d1;
cout << boolalpha << (d1=Ò) << endl;
cout << (memcmp(&d1, &d2, sizeof (double)) == 0) << endl;
return 0;
}


La même chose avec
double d1 = 0.0;
double d2 = -0.0;
est également intéressant ...

Falk

Avatar
Sylvain
Loïc Joly wrote on 12/09/2006 00:44:

Mon compilateur (VC++8.0) reproduit très bien ce comportement avec le
code suivant :
#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
double d2 = d1;


ce n'est pas son code ! à la première lecture j'ai failli répondre que
la copie d'un NaN pouvait donner autre chose (binairement).

mais son code est:

double x = std::numeric_limits<double>::quiet_NaN();
std::cout << std::boolalpha
<< x
<< ' '
<< (x == x)
etc

cout << boolalpha << (d1=Ò) << endl;


ici "d1=Ò" évalué à false me parait normal (en tout cas non surprenant).


Là, il y a le fait qu'en général, les attributs mutables servent à
définir des informations purement techniques (l'état ou la valeur d'un
cache de calcul), et qu'elles ne font pas partie de l'état observable
d'une instance. Rien n'oblige ça, mais ce serait un très mauvais style
de programmation d'utiliser mutable sans respecter cette condition.

Comme elles ne font pas partie de l'état observable, il serait
intéressant de les sauter lors d'une comparaison membre à membre générée
automatiquement.


je suis entièrement d'accord vu comme cela.

le thread aurait pu en effet discourir de ce qui serait intéressant pour
un tel opérateur généré automatiquement, et sur ce point j'ai envie de
me ranger à votre avis; mais ça n'a pas été le cas.

<réponse automatique de Gabriel (pour t'éviter cette peine)>
"et on ne se demande pas pourquoi."
</réponse automatique de Gabriel>

Sylvain.

Avatar
Sylvain
Falk Tannhäuser wrote on 12/09/2006 00:56:

La même chose avec
double d1 = 0.0;
double d2 = -0.0;
est également intéressant ...



j'attends
"==" 'vrai'
memcmp 'faux'

est-ce cela (pas de gcc sous la main pour vérifier).

si c'est le cas c'est normal, car
d1 = 000000000000000000000000b (je les mets pas tous)
d2 = 100000000000000000000000b

donc le memcmp trouve bien une différence; l'opérateur ==(double,
double) du runtime, lui sait que -0.0 et 0.0 sont "numériquement"
identiques même si binairement différent, il renvoie donc vrai.

Sylvain.

Avatar
Loïc Joly
Loïc Joly schrieb:

#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
double d2 = d1;
cout << boolalpha << (d1=Ò) << endl;
cout << (memcmp(&d1, &d2, sizeof (double)) == 0) << endl;
return 0;
}



La même chose avec
double d1 = 0.0;
double d2 = -0.0;
est également intéressant ...


Pour moi, dans ce cas, il retourne true, true. Je ne sais pas si c'est
un résultat conforme. Par contre, pour
double d1 = 0;
double d2 = -d1;
Il retourne bien true, false.

--
Loïc


Avatar
Loïc Joly
Loïc Joly wrote on 12/09/2006 00:44:


Mon compilateur (VC++8.0) reproduit très bien ce comportement avec le
code suivant :
#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
double d2 = d1;



ce n'est pas son code ! à la première lecture j'ai failli répondre que
la copie d'un NaN pouvait donner autre chose (binairement).

mais son code est:

double x = std::numeric_limits<double>::quiet_NaN();
std::cout << std::boolalpha
<< x
<< ' '
<< (x == x)
etc



J'ai réécrit le code

#include "stdafx.h"
#include <cassert>
#include <float.h>
#include <iostream>

using namespace std;

int main()
{
double num = 0;
double denum = 0;
double d1 = num/denum;
assert(_isnan(d1));
cout << boolalpha << (d1=Ñ) << endl;
cout << (memcmp(&d1, &d1, sizeof (double)) == 0) << endl;
return 0;
}

J'ai le même résultat. J'ai utilisé directement le code en question,
j'obtiens en sortie "1.#QNAN false"

--
Loïc


Avatar
Sylvain
Loïc Joly wrote on 12/09/2006 01:06:

Mon compilateur (VC++8.0) reproduit très bien ce comportement
[...]
cout << boolalpha << (d1=Ñ) << endl;



J'ai le même résultat. J'ai utilisé directement le code en question,
j'obtiens en sortie "1.#QNAN false"



avec VC6, j'obtiens "true". le code:

cout << boolalpha << (d1=Ñ) << endl;

génère:

fld qword ptr[ebp-18h] // load real d1 (ST0 := d1)
fcomp qword ptr[ebp-18h] // compare real & pop (cmp, pop ST0)
fnstsw ax // store status word to ax (ax = stat = 7D01)
test ah,40h // (ah & 0x40) ?

cela me parait valide et positionne bien le résultat de l'évaluation à
'true'.

je ne sais si cela est applicable ici, mais une note traine sur la toile:
<<When operating a Pentium® or Intel486™ processor in MS-DOS* operating
system compatibility mode, it is possible (under unusual circumstances)
for an FNSTSW instruction to be interrupted prior to being executed to
handle a pending FPU exception. See the section titled "No-Wait FPU
Instructions Can Get FPU Interrupt in Window" in Appendix D of the IA-32
Intel® Architecture Software Developer's Manual, Volume 1, for a
description of these circumstances>> (la farce est censé disparaître à
partir du Pentium pro).

est-ce votre FPU se reset trop tôt ?

Sylvain.



Avatar
Sylvain
Loïc Joly wrote on 12/09/2006 00:50:

"==" n'est pas univoque. Il peut y avoir pléthore de relations
d'équivalence sur des objets. Par exemple, prenons une classe de fraction.

Il est tout à fait légitime et mathématiquement accepté d'écrire un
opérateur == pour une telle classe qui retourne vrai lorqu'il compare
1/2 et 2/4.

[...]

L'opérateur à implémenter dépend du contexte d'utilisation, de l'état
d'esprit du développeur,... Il n'a rien d'univoque.


j'ai déjà répondu à ce point dans les échanges avec Franck ("quotient
{n=6, d=2} est-il égal à int { 3 };").

s'interroger sur le fonctionnement d'un opérateur par défaut n'a jamais
impliqué de perdre cette possibilité de définition; bien sur qu'il
faudra "écrire un opérateur == pour [cette] classe" (et que celui-ci
utilise un moteur de simplification si besoin).

pour la sémantique, ce qu' "évoque" une égalité reste "uni"que; le fait
que nous faisons correspondre à plusieurs entités (2/4 et 1/2 ou 5dm et
0.5m) la même réalité, quantité ou sentiment ne change pas cela.
vérifier une égalité peut être fait de mille façons (et peux comparer
des choses différentes) mais cela ne change pas la sa nature (identité)
et à chaque fois vous direz bien "c'est égal" (ou pas).

Sylvain.

Avatar
Loïc Joly

avec VC6, j'obtiens "true". le code:

cout << boolalpha << (d1=Ñ) << endl;

génère:

fld qword ptr[ebp-18h] // load real d1 (ST0 := d1)
fcomp qword ptr[ebp-18h] // compare real & pop (cmp, pop ST0)
fnstsw ax // store status word to ax (ax = stat = 7D01)
test ah,40h // (ah & 0x40) ?


VC8 me génère pour ma part :
00411555 fld qword ptr [d1]
00411558 fcomp qword ptr [d1]
0041155B fnstsw ax
0041155D test ah,44h

cela me parait valide et positionne bien le résultat de l'évaluation à
'true'.


Ca me parait valide, et positionne bien le résultat à 'false' ;). Plus
sérieusement, n'ayant jamais fait d'assembleur, je ne sais pas trop à
quoi correspond ce 40h(VC6) 44h(VC8).

après exécution de la ligne 0041155B, j'ai ax = 0x4501.


je ne sais si cela est applicable ici, mais une note traine sur la toile:
<<When operating a Pentium® or Intel486™ processor in MS-DOS* operating
system compatibility mode, it is possible (under unusual circumstances)
for an FNSTSW instruction to be interrupted prior to being executed to
handle a pending FPU exception. See the section titled "No-Wait FPU
Instructions Can Get FPU Interrupt in Window" in Appendix D of the IA-32
Intel® Architecture Software Developer's Manual, Volume 1, for a
description of these circumstances>> (la farce est censé disparaître à
partir du Pentium pro).

est-ce votre FPU se reset trop tôt ?


Il se porte très bien, merci... J'ai un AMD athlon XP, et ne pense donc
pas être dans ces cas étranges. Qu'est-ce qui pose problème avec le
résultat que j'obtiens ?

--
Loïc

4 5 6 7 8