Ich lerne über das Überladen von Operatoren in C ++, und ich sehe das ==
und !=
sind einfach einige spezielle Funktionen, die für benutzerdefinierte Typen angepasst werden können. Mein Anliegen ist jedoch, warum zwei separate Definitionen erforderlich sind. Ich dachte, wenn a == b
es wahr ist, dann a != b
ist es automatisch falsch und umgekehrt, und es gibt keine andere Möglichkeit, weil es per Definition a != b
ist !(a == b)
. Und ich konnte mir keine Situation vorstellen, in der dies nicht stimmte. Aber vielleicht ist meine Vorstellungskraft begrenzt oder ich weiß nichts?
Ich weiß, dass ich eins in Bezug auf das andere definieren kann, aber das ist nicht das, worüber ich frage. Ich frage auch nicht nach der Unterscheidung zwischen dem Vergleichen von Objekten nach Wert oder Identität. Oder ob zwei Objekte gleichzeitig gleich und ungleich sein könnten (dies ist definitiv keine Option! Diese Dinge schließen sich gegenseitig aus). Was ich frage, ist Folgendes:
Gibt es eine Situation, in der es Sinn macht, Fragen zu stellen, ob zwei Objekte gleich sind, aber zu fragen, ob sie nicht gleich sind, macht keinen Sinn? (entweder aus Sicht des Benutzers oder aus Sicht des Implementierers)
Wenn es keine solche Möglichkeit gibt, warum in C ++ werden diese beiden Operatoren dann als zwei unterschiedliche Funktionen definiert?
quelle
'undefined' != expression
immer wahr (oder falsch oder undefiniert) ist, unabhängig davon, ob der Ausdruck ausgewertet werden kann. In diesem Falla!=b
würde das korrekte Ergebnis gemäß Definition zurückgegeben, es!(a==b)
würde jedoch fehlschlagen, wennb
es nicht ausgewertet werden kann. (Oder nehmen Sie sich viel Zeit, wenn die Bewertungb
teuer ist).(NaN != NaN) == true
Antworten:
Sie möchten nicht, dass die Sprache automatisch neu geschrieben wird
a != b
,!(a == b)
wenna == b
etwas anderes als a zurückgegeben wirdbool
. Und es gibt einige Gründe, warum Sie das dazu bringen könnten.Möglicherweise verfügen Sie über Expression Builder-Objekte, bei denen
a == b
kein Vergleich durchgeführt werden soll, sondern lediglich ein Ausdrucksknoten erstellt wirda == b
.Möglicherweise haben Sie eine verzögerte Auswertung, bei
a == b
der kein Vergleich direkt durchgeführt werden soll oder nicht, sondern eine Art zurückgegeben wirdlazy<bool>
, die zubool
einem späteren Zeitpunkt implizit oder explizit konvertiert werden kann, um den Vergleich tatsächlich durchzuführen. Möglicherweise mit den Expression Builder-Objekten kombiniert, um eine vollständige Ausdrucksoptimierung vor der Auswertung zu ermöglichen.Möglicherweise haben Sie eine benutzerdefinierte
optional<T>
Vorlagenklasse, in der optionale Variablen angegeben sindt
und dieu
Sie zulassen möchtent == u
, aber zurückgebenoptional<bool>
.Es gibt wahrscheinlich mehr, an das ich nicht gedacht habe. Und obwohl in diesen Beispielen die Operation
a == b
unda != b
beides sinnvoll sind, ist dies immer nocha != b
nicht dasselbe wie!(a == b)
, sodass separate Definitionen erforderlich sind.quelle
!=
Berechnung anstelle von zwei Durchgängen==
dann!
. Besonders damals, als man sich nicht darauf verlassen konnte, dass der Compiler die Schleifen zusammenführt. Oder auch heute noch, wenn Sie den Compiler nicht überzeugen können, überlappen sich Ihre Vektoren nicht.!
können auch einige Ausdruck Knoten bauen und wir sind immer noch in Ordnung zu ersetzena != b
mit!(a == b)
, so weit das geht. Gleiches gilt fürlazy<bool>::operator!
, es kann zurückkehrenlazy<bool>
.optional<bool>
ist überzeugender, da die logische Wahrhaftigkeit von beispielsweiseboost::optional
davon abhängt, ob ein Wert existiert, nicht vom Wert selbst.Nan
s - bitte erinnere dich an dasNaN
s;Weil Sie sie überladen können und indem Sie sie überladen, können Sie ihnen eine völlig andere Bedeutung geben als ihre ursprüngliche.
Nehmen wir zum Beispiel den Operator
<<
, ursprünglich den bitweisen Linksverschiebungsoperator, der jetzt üblicherweise als Einfügeoperator überladen ist, wie instd::cout << something
; völlig andere Bedeutung als das Original.Wenn Sie also akzeptieren, dass sich die Bedeutung eines Operators ändert, wenn Sie ihn überladen, gibt es keinen Grund, den Benutzer daran zu hindern, dem Operator eine Bedeutung zu geben
==
, die nicht genau die Negation des Operators ist!=
, obwohl dies verwirrend sein kann.quelle
==
und!=
existieren als verschiedene Operatoren. Auf der anderen Seite existieren sie wahrscheinlich nicht als separate Operatoren, da Sie sie separat überladen können, sondern aus Gründen des Legacy und der Bequemlichkeit (Code-Kürze).Sie müssen nicht beide definieren.
Wenn sie sich gegenseitig ausschließen, können Sie dennoch präzise sein, indem Sie nur
==
und<
neben std :: rel_ops definierenAus Bezug:
Wir verbinden diese Operatoren häufig mit Gleichheit.
Obwohl sie sich bei grundlegenden Typen so verhalten, besteht keine Verpflichtung, dass dies ihr Verhalten bei benutzerdefinierten Datentypen ist. Sie müssen nicht einmal einen Bool zurückgeben, wenn Sie nicht möchten.
Ich habe gesehen, wie Leute Operatoren auf bizarre Weise überladen, nur um festzustellen, dass dies für ihre domänenspezifische Anwendung sinnvoll ist. Selbst wenn die Benutzeroberfläche zu zeigen scheint, dass sie sich gegenseitig ausschließen, möchte der Autor möglicherweise eine bestimmte interne Logik hinzufügen.
Ich weiß, dass Sie ein bestimmtes Beispiel möchten.
Hier ist eines aus dem Catch-Test-Framework , das ich für praktisch hielt:
Diese Operatoren machen verschiedene Dinge, und es wäre nicht sinnvoll, eine Methode als! (Nicht) der anderen zu definieren. Der Grund dafür ist, dass das Framework den durchgeführten Vergleich ausdrucken kann. Dazu muss der Kontext erfasst werden, in dem der überladene Operator verwendet wurde.
quelle
std::rel_ops
? Vielen Dank, dass Sie darauf hingewiesen haben.rel_ops
ist sowieso schrecklich.Es gibt einige sehr gut etablierte Konventionen , in denen
(a == b)
und(a != b)
sindbeide falschnicht unbedingt Gegensätze. Insbesondere in SQL ergibt jeder Vergleich mit NULL NULL, nicht wahr oder falsch.Es ist wahrscheinlich keine gute Idee, wenn möglich neue Beispiele dafür zu erstellen, da dies so unintuitiv ist. Wenn Sie jedoch versuchen, eine vorhandene Konvention zu modellieren, ist es hilfreich, die Option zu haben, dass sich Ihre Operatoren dafür "korrekt" verhalten Kontext.
quelle
NULL == something
Unbekannt zurückgeben, und Sie möchten auchNULL != something
Unbekannt zurückgeben, und Sie möchten!Unknown
zurückkehrenUnknown
. Und in diesem Fall ist die Implementierungoperator!=
als Negation vonoperator==
immer noch korrekt.operator==
oderoperator!=
das andere zu implementieren , aber nicht das andere, oder 2) esoperator!=
auf eine andere Weise als die Negation von zu implementierenoperator==
. Und die Implementierung von SQL-Logik für NULL-Werte ist kein Fall.Ich werde nur den zweiten Teil Ihrer Frage beantworten, nämlich:
Ein Grund, warum es sinnvoll ist, dem Entwickler zu erlauben, beide zu überlasten, ist die Leistung. Sie können Optimierungen zulassen, indem Sie sowohl
==
als auch implementieren!=
. Dannx != y
könnte billiger sein als!(x == y)
ist. Einige Compiler können es möglicherweise für Sie optimieren, aber möglicherweise nicht, insbesondere wenn Sie komplexe Objekte mit viel Verzweigung haben.Selbst in Haskell, wo Entwickler Gesetze und mathematische Konzepte sehr ernst nehmen, darf man beide überladen,
==
und/=
wie Sie hier sehen können ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude) .html # v: -61--61- ):Dies würde wahrscheinlich als Mikrooptimierung angesehen, könnte jedoch in einigen Fällen gerechtfertigt sein.
quelle
pcmpeqb
Anweisung, aber keine gepackte Vergleichsanweisung, die eine! = Maske erzeugt. Wenn Sie also nicht einfach die Logik der Ergebnisse umkehren können, müssen Sie eine andere Anweisung verwenden, um sie zu invertieren. (Unterhaltsame Tatsache: AMDs XOP-Befehlssatz hat gepackte Vergleiche fürneq
. Schade, dass Intel XOP nicht übernommen / erweitert hat; es gibt einige nützliche Anweisungen in dieser bald toten ISA-Erweiterung.)PXOR
mit allen Anweisungen zum Invertieren des Vergleichsmaskenergebnisses) in einer engen Schleife kann von Bedeutung sein.x == y
wesentlich höher sind alsx != y
. Letzteres zuDas ist eine Meinung. Vielleicht nicht. Da die Sprachdesigner jedoch nicht allwissend waren, beschlossen sie, Menschen, die möglicherweise auf Situationen stoßen, in denen dies (zumindest für sie) sinnvoll sein könnte, nicht einzuschränken.
quelle
Als Antwort auf die Bearbeitung;
Im Allgemeinen macht es keinen Sinn. Gleichheits- und Vergleichsoperatoren kommen im Allgemeinen in Mengen vor. Wenn es die Gleichheit gibt, dann auch die Ungleichheit; kleiner als, dann größer als und so weiter mit dem
<=
usw. Ein ähnlicher Ansatz wird auch auf die arithmetischen Operatoren angewendet, sie kommen im Allgemeinen auch in natürlichen logischen Mengen vor.Dies zeigt sich in der
std::rel_ops
Namespace belegt. Wenn Sie die Gleichheit und weniger als Operatoren implementieren, erhalten Sie bei Verwendung dieses Namespace die anderen, die in Bezug auf Ihre ursprünglich implementierten Operatoren implementiert wurden.Gibt es Bedingungen oder Situationen, in denen das eine nicht unmittelbar das andere bedeuten würde oder nicht in Bezug auf die anderen umgesetzt werden könnte? Ja, es gibt wohl nur wenige, aber sie sind da; wieder, wie sich im
rel_ops
Sein eines eigenen Namespace zeigt. Aus diesem Grund können Sie durch die unabhängige Implementierung der Sprache die Sprache nutzen, um die gewünschte oder benötigte Semantik auf eine Weise zu erhalten, die für den Benutzer oder Client des Codes immer noch natürlich und intuitiv ist.Die bereits erwähnte träge Bewertung ist ein hervorragendes Beispiel dafür. Ein weiteres gutes Beispiel ist die Semantik, die überhaupt keine Gleichheit oder Ungleichheit bedeutet. Ein ähnliches Beispiel dafür ist die Bitverschiebung Betreiber
<<
und>>
für den Strom Einführen und Herausziehen verwendet wird. Obwohl es in allgemeinen Kreisen verpönt sein mag, kann es in einigen domänenspezifischen Bereichen sinnvoll sein.quelle
Wenn die Operatoren
==
und!=
tatsächlich keine Gleichheit implizieren, genauso wie die<<
und>>
Operatoren stream keine Bitverschiebung. Wenn Sie die Symbole so behandeln, als ob sie ein anderes Konzept bedeuten, müssen sie sich nicht gegenseitig ausschließen.In Bezug auf die Gleichheit kann es sinnvoll sein, wenn Ihr Anwendungsfall die Behandlung von Objekten als nicht vergleichbar rechtfertigt, sodass jeder Vergleich false zurückgeben sollte (oder einen nicht vergleichbaren Ergebnistyp, wenn Ihre Operatoren non-bool zurückgeben). Ich kann mir keine bestimmte Situation vorstellen, in der dies gerechtfertigt wäre, aber ich könnte sehen, dass dies vernünftig genug ist.
quelle
Mit großer Kraft kommt verantwortungsbewusst oder zumindest wirklich gute Styleguides.
==
und!=
kann überladen werden, um zu tun, was zum Teufel Sie wollen. Es ist sowohl ein Segen als auch ein Fluch. Es gibt keine Garantie , dass!=
Mittel!(a==b)
.quelle
Ich kann diese Überladung des Operators nicht rechtfertigen, aber im obigen Beispiel ist es unmöglich,
operator!=
das "Gegenteil" von zu definierenoperator==
.quelle
!=
würde dann ja nicht das Gegenteil von bedeuten==
.==
?Am Ende überprüfen Sie mit diesen Operatoren, ob der Ausdruck
a == b
odera != b
ein boolescher Wert (true
oderfalse
) zurückgegeben wird. Dieser Ausdruck gibt nach dem Vergleich einen Booleschen Wert zurück, anstatt sich gegenseitig auszuschließen.quelle
Eine zu berücksichtigende Sache ist, dass es die Möglichkeit gibt, einen dieser Operatoren effizienter zu implementieren, als nur die Negation des anderen zu verwenden.
(Mein Beispiel hier war Müll, aber der Punkt bleibt bestehen. Denken Sie an Bloom-Filter, zum Beispiel: Sie ermöglichen schnelle Tests, wenn etwas nicht in einem Set enthalten ist, aber das Testen, ob es vorhanden ist, kann viel länger dauern.)
Und es liegt in Ihrer Verantwortung als Programmierer, dies zu gewährleisten. Wahrscheinlich eine gute Sache, um einen Test zu schreiben.
quelle
!((a == rhs.a) && (b == rhs.b))
keinen Kurzschluss? Wenn!(a == rhs.a)
, dann(b == rhs.b)
wird nicht ausgewertet.==
wird der Vergleich beendet, sobald die ersten entsprechenden Elemente ungleich sind. Aber im Falle der!=
, wenn er in Bezug auf die durchgeführt wurden==
, wäre es müssen zuerst alle die entsprechenden Elemente vergleichen (wenn sie alle gleich sind) in der Lage sein zu sagen , dass sie nicht nicht-gleich: P Aber wenn sie umgesetzt werden, wie in Im obigen Beispiel wird der Vergleich beendet, sobald das erste ungleiche Paar gefunden wird. Ein großartiges Beispiel.!((a == b) && (c == d))
und(a != b) || (c != d)
sind in Bezug auf die Kurzschlusseffizienz gleichwertig.Durch Anpassen des Verhaltens der Operatoren können Sie sie dazu bringen, das zu tun, was Sie wollen.
Möglicherweise möchten Sie die Dinge anpassen. Beispielsweise möchten Sie möglicherweise eine Klasse anpassen. Objekte dieser Klasse können nur durch Überprüfen einer bestimmten Eigenschaft verglichen werden. Wenn Sie wissen, dass dies der Fall ist, können Sie einen bestimmten Code schreiben, der nur die minimalen Dinge überprüft, anstatt jedes einzelne Bit jeder einzelnen Eigenschaft im gesamten Objekt zu überprüfen.
Stellen Sie sich einen Fall vor, in dem Sie herausfinden können, dass etwas genauso schnell, wenn nicht sogar schneller anders ist, als Sie herausfinden können, dass etwas dasselbe ist. Zugegeben, sobald Sie herausgefunden haben, ob etwas gleich oder verschieden ist, können Sie das Gegenteil erkennen, indem Sie einfach ein wenig umdrehen. Das Umdrehen dieses Bits ist jedoch eine zusätzliche Operation. In einigen Fällen kann das Speichern einer Operation (multipliziert mit einem Vielfachen) zu einer Erhöhung der Gesamtgeschwindigkeit führen, wenn der Code häufig erneut ausgeführt wird. (Wenn Sie beispielsweise einen Vorgang pro Pixel eines Megapixel-Bildschirms speichern, haben Sie gerade eine Million Vorgänge gespeichert. Multipliziert mit 60 Bildschirmen pro Sekunde und Sie speichern noch mehr Vorgänge.)
Die Antwort von hvd enthält einige zusätzliche Beispiele.
quelle
Ja, weil einer "gleichwertig" und ein anderer "nicht gleichwertig" bedeutet und sich diese Begriffe gegenseitig ausschließen. Jede andere Bedeutung für diesen Operator ist verwirrend und sollte auf jeden Fall vermieden werden.
quelle
a != b
nicht gleich ist!(a == b)
?Vielleicht eine unvergleichbar Regel wo
a != b
war falsch unda == b
war falsch wie ein stateless Bit.quelle
operator==()
undoperator!=()
nicht unbedingtbool
, sie können eine Aufzählung sein, die zustandslos enthält, wenn Sie das wollten, und dennoch können die Operatoren so definiert werden, dass sie gelten(a != b) == !(a==b)
.