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

1 2 3 4 5
Avatar
Franck Branjonneau
Sylvain écrivait:

James Kanze wrote on 02/09/2006 18:31:

En tout cas ma question est valable pour le C aussi. Pourquoi
la norme ne permet pas de comparer des structures comme il est
possible de faire des affectations?


Probablement parce qu'on n'en a pas vu l'intérêt. Après tout, si
on va implémenter ==, pourquoi pas > aussi. Voire même +.


"==" est univoque


Non.

"==" pourrait être implicite avec un (par exemple):
typeof(a) == typeof(b) && memcmp(@a, @b, sizeof(a)) == 0


Tu inclus donc dans tous tes headers quelques fonctions telles que (par
exemple) :

template< typename _L, typename _R >
bool
operator==(_L const &, _R const &) {

return false;
}

template< typename _T >
bool
operator==(_T const & l, _T const & r) {

return memcmp(&l, &r, sizeof(_T));
}

?

--
Franck Branjonneau



Avatar
Franck Branjonneau
Sylvain écrivait:

Fabien LE LEZ wrote on 02/09/2006 21:32:

Pourquoi n'est-il pas prévu qu'il fasse de même pour l'opérateur
(par exemple) de comparaison (==)?


Un opérateur == par défaut apporterait plus de problèmes qu'il n'en
résoudrait. En effet, à chaque fois qu'il ne correspond pas, et qu'on
n'en a pas besoin, il faudrait le déclarer privé (comme pour = ).


"plus de problèmes" parce qu'il existerait qlq exceptions ?!? j'ai du
mal à suivre !


Si dans ton code les exceptions sont rarissimes, peut-être sont-elles
lègions dans le mien ?

s'il ne correspond pas, on le (re)définit (localement), n'est-ce pas
le propre d'un langage objet ?


Non. Si l'égalité n'a pas de sens pour un type je ne la définis pas
pour ce type. Le problème n'est pas tant sa définition, modifiable,
mais sa déclaration qui ne l'est pas.

quant à savoir si "on" en a besoin ou pas, je ne connais pas ce
monsieur


Vraiment ?

s'il ne correspond pas, on le (re)définit (localement), n'est-ce pas
le propre d'un langage objet ?


(sic)

Par contre, ce qui serait bien, c'est un sucre syntaxique qui
permettrait, dans une classe "simple" comme [...]
de pouvoir déclarer, simplement mais explicitement, un constructeur

C::C (int const& x_, std::string const& machin_, double const& truc_)
: x (x_), machin (machin_), truc (truc_) {}


"explicitement" tu peux déjà écrire
C::C (int x_, std::string const& machin_, double truc_)
c'est un peu plus court et strictement équivalent.

Mais de toutes façons, ça ne doit pas être implicite.


tu veux dire: "il est évident" que ça ne doit pas être implicite?


Il est évident que ce ne doit pas être implicite.

--
Franck Branjonneau



Avatar
Franck Branjonneau
Sylvain écrivait:

Franck Branjonneau wrote on 02/09/2006 23:44:
[...]

Non. Si l'égalité n'a pas de sens pour un type je ne la définis pas
pour ce type. Le problème n'est pas tant sa définition, modifiable,
mais sa déclaration qui ne l'est pas.


si une égalité n'a pas de sens je ne l' *utilise* pas; elle ferait
parti d'opérateurs implicites que cela ne me gênerait pas (non, je ne
cauchemarderais pas l'angoisse d'avoir tapé un "==" à l'insu de mon
plein gré).


C'est plus que raisonnable. Cependant, l'usage a montré qu'il valait mieux
déclarer un constructeur de copie privé plutôt que de le laisser en libre
accès lorsqu'il n'a pas de sens.

Mais de toutes façons, ça ne doit pas être implicite.
tu veux dire: "il est évident" que ça ne doit pas être implicite?



Il est évident que ce ne doit pas être implicite.


c'est ce que je pensais.
Cf msg-ID


Tu te trompes d'interlocuteur.

--
Franck Branjonneau




Avatar
Franck Branjonneau
Sylvain écrivait:

Franck Branjonneau wrote on 02/09/2006 23:36:

"==" est univoque


Non.


tu fais bien de ne pas détailler, ça évite les bourdes.
(si '0' n'est plus égal à '0' et '1' à '1' on cours au pb).


Mais '0' égale '0' et '0' == '0' ; c'est fixé par le langage.
Par contre, la sémantique de operator== ne l'est pas ; son utilisation
à d'autres fins que la représentation de l'égalité est envisageable.
Enfin, même quand "==" est "égaler" est-ce nécessairement la vraie
égalité -- la diagonale de TxT pour un type T ? En pratique, je pense
que non. Et en théorie :

struct S {};

S s1, s2;

s1 == s2 est-il vrai ou faux ?

--
Franck Branjonneau



Avatar
James Kanze
Olivier Croquette wrote:
Franck Branjonneau wrote:
Non. Que les fonctions membres définies par le compilateur
suffisent à assurer la compatibilité "ascendante" avec le C.


Est-ce que cela veut dire que si le C n'avait pas permis
l'affectation de structures, le C++ n'aurait pas défini non
plus les opérateurs par défaut qu'il définit?


Ça me semble évident.

Pourtant, pour des classes ou des structures avec que des
membres simples, il est un peu idiot de devoir définir
manuellement des fonctions ou méthodes qui font membre à
membre la recopie ou la comparaison.


C'est encore pire que d'avoir un fonctionnement qu'on ne veut
pas forcément.

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

Pourtant, pour des classes ou des structures avec que des membres
simples, il est un peu idiot de devoir définir manuellement des
fonctions ou méthodes qui font membre à membre la recopie ou la
comparaison.


Peut-être, mais prenons le problème à l'envers: tu ne trouves
pas encore plus idiot de devoir déclarer manuellement
l'opérateur d'assignation et le constructeur de copie en tant
que private si tu ne veux pas que ton objet soit copiable?


Déclaration qu'on oublie facilement, et dont l'oublie ne se
rémarque pas tant qu'on se sert de la classe correctement. Puis,
un jour, quelqu'un a une copie inattendue, et on cherche
pourquoi des modifications à l'objet ne sont pas visible
partout.

[...]
Je ne voudrais pas prendre mon cas pour une généralité, mais
dans les projets que j'ai fait pour l'instant j'ai beaucoup
plus de classes qui dérivent de boost::noncopyable que de
classes pour lesquelles j'ai du écrire un opérateur d'égalité.

Et si je peux oublier de mettre des classes en noncopyable
alors qu'elles le devraient, je me rend compte assez vite
qu'un opérateur d'égalité est manquant.

Bref, la sémantique de valeur est assez minoritaire, en général, j' ai
l'impression.


Ça dépend de l'application, où peut-être plutôt à quel niveau on
y travaille. Je définis quand même pas mal d'objets
« valeur ». Mais même avec les objets valeur, l'opérateur
d'affectation par défaut ne fait pas toujours l'affaire.

Avoir un défaut n'est réelement valable que quand il convient
pour la plus grande partie des utilisations. Ici, ce n'est
clairement pas le cas.

--
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
Sylvain wrote:
James Kanze wrote on 02/09/2006 18:31:

En tout cas ma question est valable pour le C aussi.
Pourquoi la norme ne permet pas de comparer des structures
comme il est possible de faire des affectations?


Probablement parce qu'on n'en a pas vu l'intérêt. Après
tout, si on va implémenter ==, pourquoi pas > aussi. Voire
même +.


"==" est univoque alors qu'une relation d'ordre sur N champs
ou de l'algèbre nécessite une définition de (quasi) groupe.


Bof. Il l'ont fait pour std::pair,

Je ne vois pas où il y a une grande différence entre == et <.

"==" pourrait être implicite avec un (par exemple):
typeof(a) == typeof(b) && memcmp(@a, @b, sizeof(a)) == 0


C-à-d donc qu'il ne fonctionnera de façon fiable qu'avec des
objets qui ne comporte que des tableaux des unsigned char. Pas
de types qui pourraient exiger du rembourrage, pas de type
flottant, pas de pointeurs, même pas des entiers signés, si on
prétend encore que le C++ doit fonctionner sur des machines qui
ne sont pas complément à deux. (Que mon code à moi néglige
parfois l'existance des telles machines, on peut l'admettre.
Après tous, des parties ne tournent de toute façon que sur un
Sparc. Mais que la norme les abandonne ? Je ne crois pas que le
comité soit près à faire ce pas.)

L'implémentation naturelle serait de considérer ses attributs
un à un, tout comme pour la recopie.


C-à-d qu'ils aient une mauvaise sémantique dès qu'il y a un
pointeur.




recopier un pointeur (sans ref. counting, etc) est un mauvais
effet de bord, comparer des adresses est bien ce que l'on
attend de "==".


Ça dépend, justement. Si le pointeur sert à la navigation, c'est
probablement ce qu'on attend (au moins qu'on souhaite qu'il
n'entre pas dans la comparaison du tout). Mais les objets qui
navige, c'est plutôt rare que la comparaison ait un sens. Où
elle pourrait avoir un sens, c'est pour les objets de valeur,
genre string, vector, etc. Et alors, s'il y a un pointeur, c'est
que l'objet pointé fait partie de la valeur, et qu'il faut
suivre le pointeur dans le comparaison.

--
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
Olivier Croquette wrote:
Question corollaire à la discussion:

Existe-t'il un moyen de comparer des structures aux membres
simples autre que la comparaison membre à membre?


Non.

J'ai une solution en tête:

Vu que le bourrage (padding) peut faire échouer une
comparaison alors que les membres sont bien égaux, je me
demande si on ne peut pas initialiser à 0 *chaque* structure:

memset((void *) &s1, 0, sizeof(s));
memset((void *) &s2, 0, sizeof(s));


Ce qui n'interdit pas au compilateur de modifier le padding par
la suite. Par exemple, donné :

struct S { char c; int i ; } ;
S s ;
char c;
// ...
s.c = c ;

Si sur la machine il s'avère que l'écriture d'un int est plus
rapide que celle d'un char (et il existe bien dess machines où
c'est le cas), le compilateur peut bien générer une écriture
d'un int dans l'affectation ci-dessus, en écrivant ce qui se
trouve par hazard dans les parties du registre non initialisé
par la lecture du char.

Puis accèder aux membres:
s1.a = 1
s2.a = 1

Et enfin les comparer:
memcmp((void *) &s1, (void *)&s2, sizeof(s));

Questions:
- est-ce assuré de marcher?


Non.

Dans la pratique, je ne connais même pas de machine où ça
marcherait, dès que le struct contient des flottants (avec un
-0.0 qui a une autre représentation de, mais qui se compare égal
à +0.0). Évidemment, le même problème se pose pour les entiers
signés sur des machines qui ne sont pas complément à deux. Et il
y a le problème des octets de rembourage que j'ai cité
ci-dessus : je connais au moins deux architectures (dont les
machines Alpha de DEC/Compaq/HP, au moins dans leurs premières
versions) où ce problème existe.

- est-ce que ça marchera en pratique courantes? Sinon sur quelles
plate-formes?


Sur aucune plate-forme que je connais. En tout cas, pas sur des
architectures Intel ni Sparc (les deux que je connais assez
bien).

Si tu te limites aux types entiers, et tu t'arranges pour que
tous les éléments soient correctement alignés sans rembourrage,
il pourrait marchait sur un petit nombre d'architectures
courantes, comme Intel ou Sparc. Mais à mon avis, même là, c'est
jouer avec le feu ; un jour ou l'autre, un programmeur de
maintenance va sûrement ajouter un champs qui ferait que
l'alignement ne tient plus, ou il ajoutera un champs flottant.

--
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
Fabien LE LEZ wrote:
On Thu, 31 Aug 2006 19:41:08 +0200, Olivier Croquette
:

Pourquoi n'est-il pas prévu qu'il fasse de même pour
l'opérateur (par exemple) de comparaison (==)?


Un opérateur == par défaut apporterait plus de problèmes qu'il
n'en résoudrait. En effet, à chaque fois qu'il ne correspond
pas, et qu'on n'en a pas besoin, il faudrait le déclarer privé
(comme pour = ).

Par contre, ce qui serait bien, c'est un sucre syntaxique qui
permettrait, dans une classe "simple" comme

struct C
{
int x;
std::string machin;
double truc;
};

de pouvoir déclarer, simplement mais explicitement, un constructeur

C::C (int const& x_, std::string const& machin_, double const& truc_)
: x (x_), machin (machin_), truc (truc_) {}

et des opérateurs == et != qui comparent membre à membre.

Mais de toutes façons, ça ne doit pas être implicite.


Je ne suis pas trop au courant, mais il me semble que Francis
Glassborow a fait une proposition dans ce sens. Je ne connais
pas les détails, mais d'après ce qu'il me semble, l'idée est que
tu déclares vouloir avoir une sémantique par défaut (mais c'est
encore à toi de dire si la fonction ainsi générée est privée,
protégée ou publique, par exemple). Que cette proposition
s'étend à des opérateurs du genre ==, pourquoi pas ? (Mais je
ne crois pas que ça fasse partie de la proposition actuelle.)

--
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
Sylvain wrote:
Fabien LE LEZ wrote on 02/09/2006 21:32:

Pourquoi n'est-il pas prévu qu'il fasse de même pour
l'opérateur (par exemple) de comparaison (==)?


Un opérateur == par défaut apporterait plus de problèmes
qu'il n'en résoudrait. En effet, à chaque fois qu'il ne
correspond pas, et qu'on n'en a pas besoin, il faudrait le
déclarer privé (comme pour = ).


"plus de problèmes" parce qu'il existerait qlq exceptions ?!?
j'ai du mal à suivre !


Peut-être parce qu'il te manque de l'expérience en C++. Déjà,
l'opérateur d'affectation et le constructeur de copie implicits
posent pas mal de problèmes. Quand l'exception devient la règle,
il y a un problème. Et des classes où le défaut que le
compilateur pourrait générer pour == doivent être une minorité
infîme. (Déjà, c'est plutôt exceptionnel qu'une classe doit
supporter ==.)

[...]
Mais de toutes façons, ça ne doit pas être implicite.


tu veux dire: "il est évident" que ça ne doit pas être
implicite?


Disons qu'il a un peu d'expérience avec le C++, connaît le
langage, et connaît les problèmes que nous posent les opérations
explicites qui existent déjà.

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



1 2 3 4 5