J'apprends la surcharge des opérateurs en C ++, et je le vois ==
et ce !=
sont simplement des fonctions spéciales qui peuvent être personnalisées pour les types définis par l'utilisateur. Cependant, je me demande pourquoi deux définitions distinctes sont nécessaires? Je pensais que si a == b
est vrai, alors a != b
automatiquement faux, et vice versa, et il n'y a pas d'autre possibilité, parce que, par définition, l' a != b
est !(a == b)
. Et je ne pouvais imaginer aucune situation dans laquelle ce n'était pas vrai. Mais peut-être que mon imagination est limitée ou que j'ignore quelque chose?
Je sais que je peux définir l'un en fonction de l'autre, mais ce n'est pas ce que je demande. Je ne demande pas non plus la distinction entre comparer des objets par valeur ou par identité. Ou si deux objets pourraient être égaux et non égaux en même temps (ce n'est certainement pas une option! Ces choses s'excluent mutuellement). Ce que je demande, c'est ceci:
Y at - il possible dans la situation où poser des questions sur deux objets étant ne égale du sens, mais poser des questions sur les pas être ne correspond pas de sens? (soit du point de vue de l'utilisateur, soit du point de vue de l'implémentateur)
S'il n'y a pas une telle possibilité, alors pourquoi diable C ++ at-il ces deux opérateurs définis comme deux fonctions distinctes?
la source
'undefined' != expression
c'est toujours vrai (ou faux ou non défini), que l'expression puisse être évaluée ou non. Dans ce casa!=b
, renvoie le résultat correct selon la définition, mais!(a==b)
échoue s'ilb
ne peut pas être évalué. (Ou prenez beaucoup de temps si l'évaluationb
coûte cher).(NaN != NaN) == true
Réponses:
Vous ne voudriez pas que la langue se réécrive automatiquement
a != b
comme!(a == b)
lorsqu'ellea == b
retourne autre chose qu'un abool
. Et il y a plusieurs raisons pour lesquelles vous pourriez le faire.Vous pouvez avoir des objets de générateur d'expression, où
a == b
ne fait pas et n'est pas destiné à effectuer une comparaison, mais construit simplement un nœud d'expression représentanta == b
.Vous pouvez avoir une évaluation paresseuse, où
a == b
n'a pas et n'est pas destiné à effectuer une comparaison directement, mais renvoie à la place une sorte delazy<bool>
cela qui peut être convertibool
implicitement ou explicitement à un moment ultérieur pour effectuer réellement la comparaison. Peut être combiné avec les objets du générateur d'expression pour permettre une optimisation complète de l'expression avant l'évaluation.Vous pouvez avoir une
optional<T>
classe de modèle personnalisée , où des variables facultatives sont fourniest
et queu
vous souhaitez autorisert == u
, mais faites-la reveniroptional<bool>
.Il y a probablement plus de choses auxquelles je n'ai pas pensé. Et même si, dans ces exemples, l'opération
a == b
eta != b
les deux ont un sens, cea != b
n'est toujours pas la même chose!(a == b)
, des définitions distinctes sont donc nécessaires.la source
!=
lieu de deux passes de calcul==
alors!
. Surtout à l'époque où vous ne pouviez pas compter sur le compilateur pour fusionner les boucles. Ou même aujourd'hui, si vous ne parvenez pas à convaincre le compilateur, vos vecteurs ne se chevauchent pas.!
peut également créer un nœud d'expression et nous pouvons toujours le remplacera != b
par!(a == b)
, pour autant que cela se passe. Il en va de mêmelazy<bool>::operator!
, il peut revenirlazy<bool>
.optional<bool>
est plus convaincant, car la véracité logique de, par exemple,boost::optional
dépend de l'existence d'une valeur et non de la valeur elle-même.Nan
s - s'il vous plaît rappelez-vous lesNaN
s;Parce que vous pouvez les surcharger, et en les surchargeant, vous pouvez leur donner un sens totalement différent de leur original.
Prenons, par exemple, l'opérateur
<<
, à l'origine l'opérateur de décalage gauche au niveau du bit, maintenant généralement surchargé en tant qu'opérateur d'insertion, comme dansstd::cout << something
; sens totalement différent de celui d'origine.Donc, si vous acceptez que la signification d'un opérateur change lorsque vous le surchargez, il n'y a aucune raison d'empêcher l'utilisateur de donner à l'opérateur une signification
==
qui n'est pas exactement la négation de l'opérateur!=
, bien que cela puisse prêter à confusion.la source
==
et!=
exister en tant qu'opérateurs distincts. D'un autre côté, ils n'existent probablement pas en tant qu'opérateurs distincts car vous pouvez les surcharger séparément, mais pour des raisons d'héritage et de commodité (brièveté du code).Vous n'avez pas besoin de définir les deux.
S'ils s'excluent mutuellement, vous pouvez toujours être concis en ne définissant
==
et à<
côté de std :: rel_opsFom cppreference:
Nous associons souvent ces opérateurs à l'égalité.
Bien que c'est ainsi qu'ils se comportent sur les types fondamentaux, il n'y a aucune obligation que ce soit leur comportement sur les types de données personnalisés. Vous n'avez même pas besoin de retourner un bool si vous ne le souhaitez pas.
J'ai vu des gens surcharger les opérateurs de manière bizarre, seulement pour découvrir que cela avait du sens pour leur application spécifique au domaine. Même si l'interface semble montrer qu'elles s'excluent mutuellement, l'auteur peut vouloir ajouter une logique interne spécifique.
Je sais que vous voulez un exemple spécifique,
alors voici celui du cadre de test Catch que je pensais pratique:
Ces opérateurs font des choses différentes, et il ne serait pas logique de définir une méthode comme un! (Pas) de l'autre. La raison pour laquelle cela est fait, est que le cadre puisse imprimer la comparaison effectuée. Pour ce faire, il doit capturer le contexte de l'opérateur surchargé utilisé.
la source
std::rel_ops
? Merci beaucoup de l'avoir signalé.rel_ops
est horrible de toute façon.Il existe des conventions très bien établies dans lesquelles
(a == b)
et qui(a != b)
sonttoutes deux faussesne sont pas nécessairement opposées. En particulier, en SQL, toute comparaison avec NULL donne NULL, pas vrai ou faux.Ce n'est probablement pas une bonne idée de créer de nouveaux exemples si cela est possible, car c'est si peu intuitif, mais si vous essayez de modéliser une convention existante, il est agréable d'avoir la possibilité de faire en sorte que vos opérateurs se comportent "correctement" pour cela le contexte.
la source
NULL == something
retourner Unknown, et vous voudriez aussiNULL != something
retourner Unknown, et vous voudriez!Unknown
revenirUnknown
. Et dans ce cas, l'implémentationoperator!=
comme la négation deoperator==
est toujours correcte.operator==
ouoperator!=
, mais pas l'autre, ou 2) de mettreoperator!=
en œuvre d'une manière autre que la négation deoperator==
. Et l'implémentation de la logique SQL pour les valeurs NULL n'est pas un cas de cela.Je ne répondrai qu'à la deuxième partie de votre question, à savoir:
L'une des raisons pour lesquelles il est logique de permettre au développeur de surcharger les deux est la performance. Vous pouvez permettre des optimisations en implémentant à la fois
==
et!=
. Alors,x != y
ça pourrait être moins cher que ça!(x == y)
. Certains compilateurs peuvent être en mesure de l'optimiser pour vous, mais peut-être pas, surtout si vous avez des objets complexes avec beaucoup de branchements impliqués.Même à Haskell, où les développeurs prennent très au sérieux les lois et les concepts mathématiques, on est toujours autorisé à surcharger les deux
==
et/=
, comme vous pouvez le voir ici ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):Cela serait probablement considéré comme une micro-optimisation, mais cela pourrait être justifié dans certains cas.
la source
pcmpeqb
instruction, mais aucune instruction de comparaison compacte produisant un masque! =. Donc, si vous ne pouvez pas simplement inverser la logique de ce qui utilise les résultats, vous devez utiliser une autre instruction pour l'inverser. (Fait amusant: le jeu d'instructions XOP d'AMD a une comparaisonneq
compacte. Dommage qu'Intel n'ait pas adopté / étendu XOP; il y a quelques instructions utiles dans cette extension ISA qui sera bientôt morte.)PXOR
avec tout-pour inverser le résultat du masque de comparaison) dans une boucle serrée peut être important.x == y
coûte beaucoup plus cherx != y
. Le calcul de ce dernier pourrait être beaucoup moins cher en raison de la prédiction de branche, etc.Voilà une opinion. Peut-être que non. Mais les concepteurs de langage, n'étant pas omniscients, ont décidé de ne pas restreindre les personnes qui pourraient trouver des situations dans lesquelles cela pourrait avoir du sens (du moins pour eux).
la source
En réponse à l'édition;
En général , non, cela n'a pas de sens. L'égalité et les opérateurs relationnels se présentent généralement en ensembles. S'il y a égalité, alors l'inégalité aussi; inférieur à, puis supérieur à et ainsi de suite avec
<=
etc. Une approche similaire est également appliquée aux opérateurs arithmétiques, ils se présentent généralement sous forme d'ensembles logiques naturels.Cela est démontré dans l'
std::rel_ops
espace de noms. Si vous implémentez l'égalité et moins d'opérateurs, l'utilisation de cet espace de noms vous donne les autres, implémentés en termes de vos opérateurs implémentés d'origine.Cela dit, existe-t-il des conditions ou des situations dans lesquelles l'une ne signifierait pas immédiatement l'autre ou ne pourrait pas être mise en œuvre en fonction des autres? Oui, il y en a , sans doute peu, mais ils sont là; encore une fois, comme en témoigne l'
rel_ops
être un espace de noms qui lui est propre. Pour cette raison, leur mise en œuvre indépendante vous permet de tirer parti du langage pour obtenir la sémantique dont vous avez besoin ou dont vous avez besoin d'une manière toujours naturelle et intuitive pour l'utilisateur ou le client du code.L'évaluation paresseuse déjà mentionnée en est un excellent exemple. Un autre bon exemple est de leur donner une sémantique qui ne signifie pas du tout l'égalité ou l'inégalité. Un exemple similaire à cela est les opérateurs de décalage de bits
<<
et>>
utilisés pour l'insertion et l'extraction de flux. Bien qu'il puisse être mal vu dans les cercles généraux, dans certains domaines spécifiques, il peut avoir un sens.la source
Si les opérateurs
==
et!=
n'impliquent pas réellement l'égalité, de la même manière que les opérateurs<<
et>>
stream n'impliquent pas de décalage de bits. Si vous traitez les symboles comme s'ils signifiaient un autre concept, ils ne doivent pas être mutuellement exclusifs.En termes d'égalité, cela pourrait avoir un sens si votre cas d'utilisation justifie de traiter les objets comme non comparables, de sorte que chaque comparaison doit retourner faux (ou un type de résultat non comparable, si vos opérateurs retournent non booléen). Je ne peux pas penser à une situation spécifique où cela serait justifié, mais je pouvais voir que c'était assez raisonnable.
la source
Avec une grande puissance vient très bien de manière responsable, ou du moins de très bons guides de style.
==
et!=
peut être surchargé pour faire tout ce que vous voulez. C'est à la fois une bénédiction et une malédiction. Il n'y a aucune garantie que cela!=
signifie!(a==b)
.la source
Je ne peux pas justifier cette surcharge d'opérateur, mais dans l'exemple ci-dessus, il est impossible de définir
operator!=
comme "l'opposé" deoperator==
.la source
!=
cela ne signifierait donc pas le contraire de==
.==
?En fin de compte, ce que vous vérifiez avec ces opérateurs, c'est que l'expression
a == b
oua != b
renvoie une valeur booléenne (true
oufalse
). Cette expression renvoie une valeur booléenne après comparaison plutôt que de s'exclure mutuellement.la source
Une chose à considérer est qu'il pourrait y avoir la possibilité de mettre en œuvre l'un de ces opérateurs plus efficacement que d'utiliser simplement la négation de l'autre.
(Mon exemple ici était des ordures, mais le point tient toujours, pensez aux filtres de floraison, par exemple: ils permettent des tests rapides si quelque chose n'est pas dans un ensemble, mais tester si c'est dans peut prendre beaucoup plus de temps.)
Et c'est votre responsabilité en tant que programmeur de maintenir cette prise. Probablement une bonne chose pour laquelle passer un test.
la source
!((a == rhs.a) && (b == rhs.b))
permet pas les courts-circuits? si!(a == rhs.a)
, alors(b == rhs.b)
ne sera pas évalué.==
, il cessera de comparer dès que les premiers éléments correspondants ne seront pas égaux. Mais dans le cas où!=
, s'il était implémenté en termes de==
, il faudrait d'abord comparer tous les éléments correspondants (quand ils sont tous égaux) pour pouvoir dire qu'ils ne sont pas différents: P Mais lorsqu'ils sont implémentés comme dans l'exemple ci-dessus, il cessera de comparer dès qu'il trouvera la première paire non égale. Un bel exemple en effet.!((a == b) && (c == d))
et(a != b) || (c != d)
sont équivalents en termes d'efficacité de court-circuit.En personnalisant le comportement des opérateurs, vous pouvez leur faire faire ce que vous voulez.
Vous voudrez peut-être personnaliser les choses. Par exemple, vous souhaiterez peut-être personnaliser une classe. Les objets de cette classe peuvent être comparés simplement en vérifiant une propriété spécifique. Sachant que c'est le cas, vous pouvez écrire du code spécifique qui ne vérifie que le minimum, au lieu de vérifier chaque bit de chaque propriété de l'objet entier.
Imaginez un cas où vous pouvez comprendre que quelque chose est différent tout aussi rapidement, sinon plus vite, que vous pouvez découvrir que quelque chose est le même. Certes, une fois que vous avez déterminé si quelque chose est identique ou différent, vous pouvez savoir le contraire simplement en retournant un peu. Cependant, retourner ce bit est une opération supplémentaire. Dans certains cas, lorsque le code est beaucoup réexécuté, l'enregistrement d'une opération (multiplié par plusieurs) peut entraîner une augmentation globale de la vitesse. (Par exemple, si vous enregistrez une opération par pixel d'un écran mégapixel, alors vous venez d'enregistrer un million d'opérations. Multiplié par 60 écrans par seconde, et vous enregistrez encore plus d'opérations.)
La réponse de hvd fournit quelques exemples supplémentaires.
la source
Oui, car l'un signifie "équivalent" et un autre "non équivalent" et ces termes s'excluent mutuellement. Toute autre signification pour ces opérateurs prête à confusion et doit être évitée par tous les moyens.
la source
a != b
n'est pas égal à!(a == b)
pour cette raison en C?Peut-être une règle incomparable, où
a != b
était faux eta == b
était faux comme un bit apatride.la source
operator==()
etoperator!=()
ne le sont pas nécessairementbool
, ils peuvent être une énumération qui inclut les apatrides si vous le souhaitez et pourtant les opérateurs peuvent toujours être définis, donc c'est vrai(a != b) == !(a==b)
.