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

StackOverflow https://stackoverflow.com/questions/217911

  •  03-07-2019
  •  | 
  •  

Question

Je suis un grand fan de laisser le compilateur faire le plus de travail possible pour vous. Lors de l'écriture d'une classe simple, le compilateur peut vous donner ce qui suit 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::string 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 poserait-il un problème? Évidemment, si la classe alloue de la mémoire, vous devez faire attention, mais pour une classe simple, le compilateur pourrait sûrement le faire pour vous?

Était-ce utile?

La solution

Le compilateur ne sait pas si vous voulez une comparaison de pointeur ou une comparaison profonde (interne).

Il est préférable de ne pas le mettre en œuvre et de laisser le programmeur le faire lui-même. Ensuite, ils peuvent faire toutes les hypothèses qu’ils aiment.

Autres conseils

L'argument selon lequel si le compilateur peut fournir un constructeur de copie par défaut, il devrait pouvoir fournir un paramètre par défaut similaire operator==() a un certain sens. Je pense que la raison de la décision de ne pas fournir de valeur par défaut générée 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):

  

Personnellement, je le trouve malheureux   que les opérations de copie sont définies par   par défaut et j'interdit la copie de   objets de beaucoup de mes cours.   Cependant, C ++ a hérité de sa valeur par défaut   cession et copie des constructeurs de   C, et ils sont fréquemment utilisés.

Donc, au lieu de & "Pourquoi C ++ n’a-t-il pas de valeur par défaut private:? &", la question aurait dû être & "; pourquoi C ++ a-t-il un constructeur par défaut d’attribution et de copie? " ;, la réponse étant que ces éléments ont été inclus à contrecœur par Stroustrup pour assurer la compatibilité ascendante avec C (probablement la cause de la plupart des verrues de C ++, mais aussi probablement la raison principale de la popularité de C ++).

Pour mes propres besoins, l’extrait que j’utilise pour les nouvelles classes dans mon IDE contient des déclarations pour un opérateur d’affectation privé et un constructeur de copie. Ainsi, lorsqu’une nouvelle classe est générée, je n’obtiens aucune opération par défaut d’affectation ni de copie. Supprimez la déclaration de ces opérations de la section <=> si je souhaite que le compilateur puisse les générer pour moi.

Même en C ++ 20, le compilateur ne génère toujours pas implicitement operator== pour vous

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

Mais vous aurez la possibilité de explicitement par défaut ==:

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

La valeur par défaut != utilise la méthode membre (de la même manière que le constructeur de copie par défaut effectue la construction de la copie membre). Les nouvelles règles fournissent également la relation attendue entre operator<=> et <=>. Par exemple, avec la déclaration ci-dessus, je peux écrire les deux:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

Cette fonctionnalité spécifique (<=> par défaut et la symétrie entre <=> et <=>) provient de une proposition cela faisait partie de la fonctionnalité linguistique plus large qui est <=> .

IMHO, il n'y a pas de " bon " raison. La raison pour laquelle tant de gens sont d’accord avec cette décision de conception est qu’ils n’ont pas appris à maîtriser le pouvoir de la sémantique fondée sur les valeurs. Les utilisateurs doivent écrire de nombreux constructeurs de copie, opérateurs de comparaison et destructeurs personnalisés, car ils utilisent des pointeurs bruts dans leur implémentation.

Lorsque vous utilisez des pointeurs intelligents appropriés (tels que 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 le serait aussi.

Il a été répondu que C ++ n’avait pas fait == parce que C ne l’avait pas fait, et voici pourquoi C ne fournit que default = mais pas == à la première place. C voulait rester simple: C implémenté = par memcpy; cependant, == ne peut pas être implémenté par memcmp en raison du bourrage. Comme le remplissage n’est pas initialisé, memcmp dit qu’elles sont différentes bien qu’elles soient identiques. Le même problème existe pour les classes vides: memcmp dit qu'elles sont différentes car la taille des classes vides n'est pas nulle. On peut voir ci-dessus que la mise en œuvre de == est plus compliquée que celle de = en C. Quelques 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 13 heures. En résumé, après avoir suivi l’évolution du C ++, il soutient que:

  • Il est regrettable que == et! = ne soient pas déclarés implicitement (et Bjarne est d'accord avec lui). Un langage correct devrait avoir ces éléments prêts pour vous (il va plus loin pour suggérer que vous ne devriez pas pouvoir 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 ++) en C. Là, l'opérateur d'affectation est défini implicitement avec une affectation bit par bit mais cela ne fonctionnerait pas pour < fort> == . Vous trouverez une explication plus détaillée dans ce article de Bjarne Stroustrup.
  • Dans la question de suivi, pourquoi la comparaison n’a-t-elle pas été utilisée membre par membre , indique-t-il une chose étonnante : C était une sorte de langage développé localement et ces choses pour Ritchie lui ont dit qu'il trouvait cela difficile à mettre en œuvre!

Il a ensuite déclaré que, dans un avenir (lointain), == et ! = seraient générés implicitement.

Il n’est pas possible de définir par défaut ==, mais vous pouvez définir par défaut != via operator< que vous devez définir vous-même. Pour cela, vous devez faire les choses suivantes:

#include <utility>
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 std::rel_ops, les opérateurs de < =, > ;, > = peuvent être déduits de <=>.

Mais vous devez faire attention lorsque vous utilisez <=> car des opérateurs de comparaison peuvent être déduits pour les types auxquels vous n'êtes pas attendu.

La meilleure façon de déduire un opérateur associé de l'opérateur de base consiste à utiliser boost :: opérateurs .

L'approche utilisée dans boost est préférable car elle définit l'utilisation de l'opérateur pour la classe que vous souhaitez uniquement, pas pour toutes les classes de la portée.

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

C ++ 20 fournit un moyen d'implémenter facilement un opérateur de comparaison par défaut.

Exemple tiré de cppreference.com :

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>

C ++ 0x a une proposition pour les fonctions par défaut, vous pouvez donc dire default operator==; Nous avons appris qu'il était utile de rendre ces choses explicites.

Conceptuellement, il n’est pas facile de définir l’égalité. Même pour les données POD, on pourrait affirmer que même si les champs sont identiques, mais qu’il s’agit d’un objet 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.

Par ailleurs, les fonctions par défaut sont d'excellents moyens de se tirer une balle dans le pied. Les valeurs par défaut que vous décrivez sont essentiellement là pour préserver la compatibilité avec les structures de POD. Cependant, ils causent plus d’assez de ravages chez les développeurs qu’ils oublient ou la sémantique des implémentations par défaut.

  

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 sur le plan fonctionnel, mais en termes de performances, la comparaison membre par membre par défaut est susceptible d’être moins optimale que la cession / copie membre par membre par défaut. Contrairement à l'ordre d'assignation, l'ordre de comparaison a un impact sur les performances car le premier membre inégal implique que le reste peut être ignoré. Donc, si certains membres sont généralement égaux, vous souhaitez les comparer en dernier et le compilateur ne sait pas quels membres ont plus de chances d'être égaux.

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

class LocalWeatherRecord {
    std::string 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 entendu, le compilateur aurait le droit de ne pas tenir compte de l'ordre des comparaisons s'il reconnaissait qu'ils n'avaient pas d'effets secondaires, mais il prendrait probablement son que du code source où il ne dispose pas de meilleures informations propre.)

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

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

En outre, ils ne tardent pas à écrire, n'est-ce pas?!

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top