Pourquoi les compilateurs C ++ ne définissent-ils pas operator == et operator! =?

Je suis un grand fan de laisser le compilateur faire autant de travail pour vous que possible. Lorsque vous écrivez une classe simple, le compilateur peut vous donner les informations suivantes pour «libre»:

  • Un constructeur par défaut (vide)
  • Un constructeur de copie
  • Un destructeur
  • Un opérateur d’affectation ( operator= )

Mais cela ne semble pas vous donner d’opérateurs de comparaison, tels que operator== ou operator!= . Par exemple:

 class foo { public: std::ssortingng str_; int n_; }; foo f1; // Works foo f2(f1); // Works foo f3; f3 = f2; // Works if (f3 == f2) // Fails { } if (f3 != f2) // Fails { } 

Y a-t-il une bonne raison à cela? Pourquoi effectuer une comparaison membre par membre serait-il un problème? De toute évidence, si la classe alloue de la mémoire, vous devriez faire attention, mais pour une classe simple, le compilateur pourrait sûrement le faire pour vous?

Le compilateur ne saura pas si vous souhaitez une comparaison de pointeurs ou une comparaison profonde (interne).

Il est plus sûr de ne pas l’implémenter et de laisser le programmeur le faire lui-même. Ensuite, ils peuvent faire toutes les hypothèses qu’ils aiment.

L’argument selon lequel si le compilateur peut fournir un constructeur de copie par défaut, il devrait être capable de fournir un operator==() par défaut similaire operator==() . Je pense que la raison de la décision de ne pas fournir un défaut généré par le compilateur pour cet opérateur peut être devinée par ce que Stroustrup a dit à propos du constructeur de copie par défaut dans “La conception et l’évolution de C ++” (Section 11.4.1 – Contrôle de la copie) :

Je trouve personnellement regrettable que les opérations de copie soient définies par défaut et que j’interdise la copie d’objects de plusieurs de mes classes. Cependant, C ++ a hérité de son atsortingbution par défaut et a copié les constructeurs de C, et ils sont fréquemment utilisés.

Donc, au lieu de “pourquoi C ++ n’a-t-il pas un operator==() par défaut operator==() ?”, La question aurait dû être “pourquoi C ++ a-t-il une atsortingbution par défaut et un constructeur de copie?” Stroustrup pour la compatibilité ascendante avec C (probablement la cause de la plupart des verrues de C ++, mais aussi probablement la principale raison de la popularité de C ++).

Pour mon propre compte, dans mon IDE, l’extrait de code que j’utilise pour les nouvelles classes contient des déclarations pour un opérateur d’affectation privé et un constructeur de copie, de sorte que lorsque je génère une nouvelle classe, de ces opérations de la section private: si je veux que le compilateur puisse les générer pour moi.

MISE À JOUR 2: Malheureusement, cette proposition n’a pas abouti à C ++ 17 , donc rien ne change dans la langue à cet égard pour le moment.

MISE À JOUR: La version actuelle de la proposition qui a de très fortes chances d’être votée en C ++ 17 est ici .

Il y a une proposition récente (N4126) sur les opérateurs de comparaison explicitement en défaut, qui a eu des réactions très positives de la part du comité standard, alors j’espère que nous la verrons sous une forme quelconque en C ++ 17.

En bref, la syntaxe proposée est la suivante:

 struct Thing { int a, b, c; std::ssortingng d; }; bool operator==(const Thing &, const Thing &)= default; bool operator!=(const Thing &, const Thing &)= default; 

Ou sous forme d’ friend pour les classes avec champs privés:

 class Thing { int a, b; friend bool operator< (Thing, Thing) = default; friend bool operator>(Thing, Thing) = default; friend bool operator< =(Thing, Thing) = default; friend bool operator>=(Thing, Thing) = default; }; 

Ou même sous une forme courte:

 struct Thing { int a, b, c; std::ssortingng d; default: ==, !=, < , >, < =, >=; // defines the six non-member functions }; 

Bien sûr, tout cela peut changer au moment où cette proposition sera finalement acceptée.

IMHO, il n’y a pas de “bonne” raison. La raison pour laquelle tant de personnes sont d’accord avec cette décision de conception est qu’elles n’ont pas appris à maîsortingser la puissance de la sémantique basée sur les valeurs. Les utilisateurs doivent écrire beaucoup de constructeurs de copie personnalisés, d’opérateurs de comparaison et de destructeurs car ils utilisent des pointeurs bruts dans leur implémentation.

Lorsque vous utilisez des pointeurs intelligents appropriés (comme std :: shared_ptr), le constructeur de copie par défaut est généralement correct et l’implémentation évidente de l’opérateur de comparaison par défaut hypothétique serait tout à fait correcte.

Il est répondu que C ++ ne l’a pas fait == car C ne l’a pas fait, et voici pourquoi C ne fournit que la valeur par défaut = mais pas == à la première place. C voulait restr simple: C implémenté = par memcpy; Cependant, == ne peut pas être implémenté par memcmp en raison du remplissage. Comme le remplissage n’est pas initialisé, memcmp dit qu’ils sont différents même s’ils sont identiques. Le même problème existe pour la classe vide: memcmp dit qu’ils sont différents car la taille des classes vides n’est pas nulle. On peut voir de haut que l’implémentation de == est plus compliquée que l’implémentation de = in C. Un exemple de code à ce sujet. Votre correction est appréciée si je me trompe.

Dans cette vidéo, Alex Stepanov, le créateur de STL, aborde cette question vers 13h00. En résumé, après avoir vu l’évolution de C ++, il soutient que:

  • Il est dommage que == et! = Ne soient pas implicitement déclarés (et Bjarne est d’accord avec lui). Un langage correct devrait avoir ces choses prêtes pour vous (il va plus loin pour suggérer que vous ne devriez pas être capable de définir un ! = Qui rompt la sémantique de == )
  • La raison pour laquelle c’est le cas a ses racines (autant de problèmes C ++) dans C. L’opérateur d’affectation est implicitement défini avec une affectation bit à bit mais cela ne fonctionnerait pas pour == . Une explication plus détaillée peut être trouvée dans cet article de Bjarne Stroustrup.
  • Dans la question de suivi Pourquoi la comparaison n’a-t-elle pas été utilisée, il dit une chose étonnante : C était une langue locale et le gars qui l’implémentait pour Ritchie lui a dit qu’il trouvait cela difficile à mettre en œuvre!

Il dit ensuite que dans le futur (distant) == et ! = Seront générés implicitement.

Il n’est pas possible de définir la valeur par défaut == , mais vous pouvez définir default != Via == que vous devez généralement vous définir. Pour cela, vous devez faire les choses suivantes:

 #include  using namespace std::rel_ops; ... class FooClass { public: bool operator== (const FooClass& other) const { // ... } }; 

Vous pouvez voir http://www.cplusplus.com/reference/std/utility/rel_ops/ pour plus de détails.

De plus, si vous définissez operator< , les opérateurs pour < =,>,> = peuvent en être déduits avec std::rel_ops .

Mais vous devez être prudent lorsque vous utilisez std::rel_ops car les opérateurs de comparaison peuvent être déduits pour les types std::rel_ops vous ne vous attendez pas.

Une manière plus préférée de déduire un opérateur lié de base consiste à utiliser les opérateurs boost :: .

L'approche utilisée dans boost est meilleure car elle définit l'utilisation de l'opérateur pour la classe que vous voulez, pas pour toutes les classes de la scope.

Vous pouvez également générer "+" à partir de "+ =", - à partir de "- =", etc ... (voir la liste complète ici )

C ++ 0x a eu une proposition pour les fonctions par défaut, vous pouvez donc dire l’ default operator==; Nous avons appris que cela aide à rendre ces choses explicites.

Juste une note, également fournie par le compilateur gratuitement:

  • opérateur nouveau
  • opérateur nouveau []
  • opérateur supprimer
  • opérateur supprimer []

Conceptuellement, il n’est pas facile de définir l’égalité. Même pour les données POD, on pourrait soutenir que même si les champs sont identiques, mais que l’object est différent (à une adresse différente), il n’est pas nécessairement égal. Cela dépend en fait de l’utilisation de l’opérateur. Malheureusement, votre compilateur n’est pas psychique et ne peut pas en déduire.

De plus, les fonctions par défaut sont d’excellentes façons de se tirer dans le pied. Les valeurs par défaut que vous décrivez sont essentiellement là pour garder la compatibilité avec les structures POD. Ils causent cependant plus de dégâts aux développeurs les oubliant, ou la sémantique des implémentations par défaut.

C ++ 20 permet d’implémenter facilement un opérateur de comparaison par défaut.

Exemple de cppreference.com :

 class Point { int x; int y; public: auto operator< =>(const Point&) const = default; // ... non-comparison functions ... }; // comstackr generates all six relational operators Point pt1, pt2; if (pt1 == pt2) { /*...*/ } // ok std::set s; // ok s.insert(pt1); // ok if (pt1 < = pt2) { /*...*/ } // ok, makes only a single call to <=> 

Je suis d’accord, pour les classes de type POD, le compilateur peut le faire pour vous. Cependant, ce que vous pourriez considérer comme simple, le compilateur pourrait se tromper. Il est donc préférable de laisser le programmeur le faire.

J’ai eu un cas de POD une fois où deux des champs étaient uniques – donc une comparaison ne serait jamais considérée comme vraie. Cependant, la comparaison dont j’avais besoin était seulement comparée sur la charge utile – quelque chose que le compilateur ne comprendrait jamais ou qu’il pourrait jamais comprendre par lui-même.

D’ailleurs, ils ne tardent pas à écrire, n’est-ce pas?!

Y a-t-il une bonne raison à cela? Pourquoi effectuer une comparaison membre par membre serait-il un problème?

Ce n’est peut-être pas un problème fonctionnel, mais en termes de performances, la comparaison par membre par défaut est susceptible d’être plus sous-optimale qu’une affectation / copie par membre par défaut. Contrairement à l’ordre d’affectation, l’ordre de comparaison a un impact sur les performances car le premier membre inégal implique que le rest peut être ignoré. Donc, si certains membres sont généralement égaux, vous voudrez les comparer en dernier et le compilateur ne sait pas quels membres sont plus susceptibles d’être égaux.

Considérez cet exemple, où verboseDescription est une longue chaîne sélectionnée parmi un ensemble relativement restreint de descriptions météorologiques possibles.

 class LocalWeatherRecord { std::ssortingng verboseDescription; std::tm date; bool operator==(const LocalWeatherRecord& other){ return date==other.date && verboseDescription==other.verboseDescription; // The above makes a lot more sense than // return verboseDescription==other.verboseDescription // && date==other.date; // because some verboseDescriptions are liable to be same/similar } } 

(Bien sûr, le compilateur aurait le droit de ne pas tenir compte de l’ordre des comparaisons s’il reconnaît qu’il n’a pas d’effets secondaires, mais il est probable qu’il ne soit pas pris en compte dans le code source.)

Les opérateurs de comparaison par défaut corrigeraient une petite quantité de temps; Je m’attends à ce qu’ils soient une source de problèmes plutôt que quelque chose d’utile.

De plus, les méthodes par défaut que vous mentionnez sont souvent indésirables. Voir le code comme celui-ci pour se débarrasser du constructeur de copie et de l’opérateur par défaut est très courant:

 class NonAssignable { // .... private: NonAssignable(const NonAssignable&); // Unimplemented NonAssignable& operator=(const NonAssignable&); // Unimplemented }; 

Dans beaucoup de code, il est courant de voir un commentaire “constructeur de copie par défaut et opérateur = OK” pour indiquer que ce n’est pas une erreur qu’ils ont été supprimés ou explicitement définis.