C ++ 20 a introduit des comparaisons par défaut, alias le "vaisseau spatial"operator<=>
, qui vous permet de demander des opérateurs <
/ <=
/ ==
/ !=
/ >=
/ et / ou générés par le compilateur >
avec l'implémentation évidente / naïve (?) ...
auto operator<=>(const MyClass&) const = default;
... mais vous pouvez personnaliser cela pour des situations plus compliquées (voir ci-dessous). Voir ici la proposition de langue, qui contient des justifications et des discussions. Cette réponse reste pertinente pour C ++ 17 et versions antérieures, et pour savoir quand vous devez personnaliser l'implémentation de operator<=>
....
Cela peut sembler un peu inutile de C ++ de ne pas l'avoir déjà standardisé plus tôt, mais les structures / classes ont souvent des données membres à exclure de la comparaison (par exemple, les compteurs, les résultats mis en cache, la capacité du conteneur, le code de réussite de la dernière opération / erreur, les curseurs), comme ainsi que des décisions à prendre sur une myriade de choses, y compris mais sans s'y limiter:
- les champs à comparer en premier, par exemple la comparaison d'un
int
membre particulier peut éliminer très rapidement 99% des objets inégaux, tandis qu'un map<string,string>
membre peut souvent avoir des entrées identiques et être relativement coûteux à comparer - si les valeurs sont chargées à l'exécution, le programmeur peut avoir des informations sur le le compilateur ne peut pas
- dans la comparaison de chaînes: respect de la casse, équivalence des espaces et des séparateurs, conventions d'échappement ...
- précision lors de la comparaison des flottants / doubles
- si les valeurs à virgule flottante NaN doivent être considérées comme égales
- comparer des pointeurs ou pointés vers des données (et dans ce dernier cas, comment savoir si les pointeurs sont vers des tableaux et combien d'objets / octets à comparer)
- si l'ordre est important lors de la comparaison de conteneurs non triés (par exemple
vector
, list
), et si oui, s'il est correct de les trier sur place avant de comparer ou d'utiliser de la mémoire supplémentaire pour trier les temporaires chaque fois qu'une comparaison est effectuée
- combien d'éléments du tableau contiennent actuellement des valeurs valides à comparer (y a-t-il une taille quelque part ou une sentinelle?)
- quel membre de a
union
comparer
- normalisation: par exemple, les types de date peuvent autoriser un jour du mois ou un mois de l'année hors de la plage, ou un objet rationnel / fraction peut avoir 6 / 8ème tandis qu'un autre en a 3/4, ce qui, pour des raisons de performance, corrige paresseusement avec une étape de normalisation séparée; vous devrez peut-être décider de déclencher une normalisation avant la comparaison
- que faire lorsque les pointeurs faibles ne sont pas valides
- comment gérer les membres et les bases qui ne s'implémentent pas
operator==
eux-mêmes (mais qui pourraient avoir compare()
ou operator<
ou str()
ou getters ...)
- quels verrous doivent être pris lors de la lecture / comparaison des données que d'autres threads peuvent vouloir mettre à jour
Donc, c'est plutôt bien d'avoir une erreur jusqu'à ce que vous ayez explicitement réfléchi à ce que la comparaison devrait signifier pour votre structure spécifique, plutôt que de la laisser compiler mais pas de vous donner un résultat significatif au moment de l'exécution .
Cela dit, ce serait bien si C ++ vous laisse dire bool operator==() const = default;
quand vous avez décidé qu'un ==
test membre par membre "naïf" était acceptable. Pareil pour !=
. Compte tenu de plusieurs membres / bases, « default » <
, <=
, >
, et >=
mises en œuvre semblent sans espoir que - en cascade sur la base de l' ordre de de déclaration possible , mais très peu de chances d'être ce qui a voulu, compte tenu des impératifs contradictoires pour la commande des membres (bases étant nécessairement avant que les membres, le regroupement par accessibilité, construction / destruction avant utilisation dépendante). Pour être plus largement utile, C ++ aurait besoin d'un nouveau système d'annotation de membre de données / base pour guider les choix - ce serait une bonne chose à avoir dans le Standard, idéalement couplé à la génération de code défini par l'utilisateur basé sur AST ... il'
Implémentation typique des opérateurs d'égalité
Une mise en œuvre plausible
Il est probable qu'une mise en œuvre raisonnable et efficace serait:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
Notez que cela nécessite un operator==
pour MyStruct2
aussi.
Les implications de cette implémentation et des alternatives sont discutées sous la rubrique Discussion des spécificités de votre MyStruct1 ci-dessous.
Une approche cohérente de ==, <,> <= etc
Il est facile de tirer parti std::tuple
des opérateurs de comparaison pour comparer vos propres instances de classe - utilisez simplement std::tie
pour créer des tuples de références aux champs dans l'ordre de comparaison souhaité. Généraliser mon exemple à partir d' ici :
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) ==
std::tie(rhs.my_struct2, rhs.an_int);
}
inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
return std::tie(lhs.my_struct2, lhs.an_int) <
std::tie(rhs.my_struct2, rhs.an_int);
}
// ...etc...
Lorsque vous "possédez" (c'est-à-dire que vous pouvez éditer un facteur avec les bibliothèques d'entreprise et tierces) la classe que vous voulez comparer, et en particulier avec la préparation de C ++ 14 à déduire le type de retour de fonction de l' return
instruction, il est souvent plus agréable d'ajouter un " liez "la fonction membre à la classe que vous voulez pouvoir comparer:
auto tie() const { return std::tie(my_struct1, an_int); }
Ensuite, les comparaisons ci-dessus se simplifient en:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
Si vous voulez un ensemble plus complet d'opérateurs de comparaison, je suggère des opérateurs de boost (recherche de less_than_comparable
). Si cela ne convient pas pour une raison quelconque, vous pouvez ou non aimer l'idée de macros de support (en ligne) :
#define TIED_OP(STRUCT, OP, GET_FIELDS) \
inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
{ \
return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
}
#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
TIED_OP(STRUCT, ==, GET_FIELDS) \
TIED_OP(STRUCT, !=, GET_FIELDS) \
TIED_OP(STRUCT, <, GET_FIELDS) \
TIED_OP(STRUCT, <=, GET_FIELDS) \
TIED_OP(STRUCT, >=, GET_FIELDS) \
TIED_OP(STRUCT, >, GET_FIELDS)
... qui peut ensuite être utilisé à la ...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(Version C ++ 14 membres-tie ici )
Discussion sur les spécificités de votre MyStruct1
Il y a des implications au choix de fournir un indépendant plutôt qu'un membre operator==()
...
Mise en œuvre autonome
Vous avez une décision intéressante à prendre. Comme votre classe peut être implicitement construite à partir de a MyStruct2
, une fonction autonome / non membre bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
prendrait en charge ...
my_MyStruct2 == my_MyStruct1
... en créant d' abord temporaire à MyStruct1
partir my_myStruct2
, puis en faisant la comparaison. Cela laisserait définitivement MyStruct1::an_int
la valeur de paramètre par défaut du constructeur de -1
. Selon que vous incluez an_int
comparaison dans la mise en œuvre de votre operator==
, un MyStruct1
pourrait ou non être égaux à un MyStruct2
qui se compare égal au MyStruct1
de my_struct_2
membre! En outre, la création d'un temporaire MyStruct1
peut être une opération très inefficace, car elle implique de copier le my_struct2
membre existant dans un temporaire, uniquement pour le jeter après la comparaison. (Bien sûr, vous pouvez empêcher cette construction implicite de MyStruct1
s pour la comparaison en créant ce constructeur explicit
ou en supprimant la valeur par défaut de an_int
.)
Implémentation des membres
Si vous souhaitez éviter la construction implicite de a à MyStruct1
partir de a MyStruct2
, faites de l'opérateur de comparaison une fonction membre:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
Notez que le const
mot-clé - uniquement nécessaire pour l'implémentation du membre - informe le compilateur que la comparaison d'objets ne les modifie pas, donc peut être autorisée sur les const
objets.
Comparaison des représentations visibles
Parfois, le moyen le plus simple d'obtenir le type de comparaison que vous souhaitez peut être ...
return lhs.to_string() == rhs.to_string();
... ce qui est souvent très cher aussi - ceux qui ont été string
créés douloureusement juste pour être jetés! Pour les types avec des valeurs à virgule flottante, la comparaison des représentations visibles signifie que le nombre de chiffres affichés détermine la tolérance dans laquelle des valeurs presque égales sont traitées comme égales lors de la comparaison.
struct
s pour l'égalité? Et si vous voulez la manière simple, il y a toujoursmemcmp
tellement longtemps que vos structures ne contiennent pas de pointeur.