Warum definieren C ++ – Compiler nicht operator == und operator! =?

Ich bin ein großer Fan davon, den Compiler so viel wie möglich für Sie arbeiten zu lassen. Wenn Sie eine einfache class schreiben, kann der Compiler Ihnen für ‘free’ folgendes geben:

  • Ein Standardkonstruktor (leer)
  • Ein Kopierkonstruktor
  • Ein Destruktor
  • Ein Zuweisungsoperator ( operator= )

Aber es kann Ihnen keine Vergleichsoperatoren geben – wie operator== oder operator!= . Beispielsweise:

 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 { } 

Gibt es einen guten Grund dafür? Warum wäre der Vergleich von Mitglied zu Mitglied ein Problem? Offensichtlich, wenn die class Speicher zuweist, sollten Sie vorsichtig sein, aber für eine einfache class könnte der Compiler das für Sie tun.

   

Der Compiler würde nicht wissen, ob Sie einen pointersvergleich oder einen tiefen (internen) Vergleich wünschen.

Es ist sicherer, es einfach nicht zu implementieren und den Programmierer das selbst machen zu lassen. Dann können sie alle Annahmen treffen, die sie mögen.

Das Argument, dass, wenn der Compiler einen Standardkopiekonstruktor bereitstellen kann, es in der Lage sein sollte, einen ähnlichen Standardoperator operator==() zu liefern, der eine gewisse Menge an Sinn ergibt. Ich denke, dass der Grund für die Entscheidung, einen vom Compiler generierten Standard für diesen Operator nicht bereitzustellen, durch das erraten wird, was Stroustrup über den Standardkopiekonstruktor in “Das Design und die Entwicklung von C ++” sagte (Abschnitt 11.4.1 – Kontrolle des Kopierens) :

Ich persönlich finde es bedauerlich, dass Kopiervorgänge standardmäßig definiert sind und ich das Kopieren von Objekten vieler meiner classn verbiete. C ++ erbte jedoch seine Standardzuweisungs- und Kopierkonstruktoren von C, und sie werden häufig verwendet.

Anstelle von “Warum hat C ++ keinen Standardoperator operator==() ?” operator==() die Frage lauten: “Warum hat C ++ eine Standardzuweisung und einen Kopierkonstruktor?”, Mit der Antwort, dass diese Elemente widerwillig von enthalten waren Stroustrup für Abwärtskompatibilität mit C (wahrscheinlich die Ursache für die meisten C ++ Warzen, aber wahrscheinlich auch der Hauptgrund für die C ++ – Popularität).

Für meine eigenen Zwecke enthält der Ausschnitt, den ich für neue classn verwende, Deklarationen für einen privaten Zuweisungsoperator und einen Kopierkonstruktor, so dass ich beim Erstellen einer neuen class keine Standardzuweisungs- und Kopieroperationen erhalte – ich muss die Deklaration explizit entfernen dieser Operationen aus dem private: Abschnitt, wenn ich möchte, dass der Compiler sie für mich generieren kann.

UPDATE 2: Leider ist dieser Vorschlag nicht zu C ++ 17 gekommen , also ändert sich in dieser Hinsicht vorerst nichts in der Sprache.

UPDATE: Die aktuelle Version des Vorschlags, der eine sehr hohe Chance hat, in C ++ 17 gewählt zu werden, ist hier .

Es gibt einen aktuellen Vorschlag (N4126) zu explizit ausgefallenen Vergleichsoperatoren, der vom Standardkomitee sehr positive Rückmeldungen erhalten hat. Hoffentlich sehen wir ihn in irgendeiner Form in C ++ 17.

Kurz gesagt, die vorgeschlagene Syntax ist:

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

Oder in friend für classn mit privaten Feldern:

 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; }; 

Oder auch in Kurzform:

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

Natürlich kann sich das alles ändern, wenn dieser Vorschlag endlich angenommen wird.

IMHO, es gibt keinen “guten” Grund. Der Grund dafür, dass so viele Menschen dieser Designentscheidung zustimmen, liegt darin, dass sie nicht gelernt haben, die Macht der wertebasierten Semantik zu beherrschen. Menschen müssen eine Menge benutzerdefinierter Kopierkonstruktoren, Vergleichsoperatoren und Destruktoren schreiben, da sie in ihrer Implementierung rohe pointers verwenden.

Bei Verwendung geeigneter Smartpointer (wie std :: shared_ptr) ist der Standardkopiekonstruktor normalerweise in Ordnung, und die offensichtliche Implementierung des hypothetischen Standardvergleichsoperators wäre genauso gut.

Es hat geantwortet C ++ hat nicht getan == weil C nicht, und hier ist, warum C nur default = aber no == auf den ersten Platz. C wollte es einfach halten: C implementiert = von memcpy; Allerdings kann == aufgrund von Auffüllung nicht von memcmp implementiert werden. Da das Padding nicht initialisiert wird, sagt memcmp, dass sie unterschiedlich sind, obwohl sie identisch sind. Dasselbe Problem existiert für leere classn: memcmp sagt, dass sie unterschiedlich sind, weil die Größe leerer classn nicht Null ist. Man kann von oben sehen, dass die Implementierung von == komplizierter ist als die Implementierung von = in C. Einige Codebeispiele dazu. Ihre Korrektur wird geschätzt, wenn ich falsch liege.

In diesem Video geht Alex Stepanov, der Schöpfer von STL, genau um 13:00 auf diese Frage ein. Zusammenfassend, nachdem er die Entwicklung von C ++ beobachtet hat, argumentiert er:

  • Es ist bedauerlich, dass == und! = Nicht implizit erklärt werden (und Bjarne stimmt ihm zu). Eine korrekte Sprache sollte diese Dinge für Sie bereithalten (er schlägt weiter vor, dass Sie nicht in der Lage sein sollten, ein ! = Zu definieren, das die Semantik von == durchbricht).
  • Der Grund dafür ist seine Wurzeln (wie viele von C ++ – Problemen) in C. Dort ist der Zuweisungsoperator implizit durch Bit-für-Bit-Zuweisung definiert, was aber für == nicht funktioniert. Eine ausführlichere Erklärung finden Sie in diesem Artikel von Bjarne Stroustrup.
  • In der anschließenden Frage Warum also kein Mitglied im Mitgliedervergleich verwendet wurde , sagt er eine erstaunliche Sache : C war eine Art Eigengewächs und der Typ, der diese Sachen für Ritchie umsetzte, sagte ihm, dass er das nur schwer umsetzen könne!

Er sagt dann, dass in der (fernen) Zukunft == und ! = Implizit erzeugt wird.

Es ist nicht möglich, default == zu definieren, aber Sie können default != Via == die Sie normalerweise selbst definieren sollten. Dazu sollten Sie folgende Dinge tun:

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

Sie können http://www.cplusplus.com/reference/std/utility/rel_ops/ für Details sehen.

Wenn Sie außerdem operator< definieren, können Operatoren für < =,>,> = daraus abgeleitet werden, wenn Sie std::rel_ops .

Aber Sie sollten vorsichtig sein, wenn Sie std::rel_ops da Vergleichsoperatoren für die Typen abgeleitet werden können, für die Sie nicht erwartet werden.

Ein bevorzugterer Weg, um einen verwandten Operator von einem Basis-Operator abzuleiten, ist boost :: operators .

Der in Boost verwendete Ansatz ist besser, da er die Verwendung von Operator für die class definiert, die Sie nur möchten, nicht für alle classn im Bereich.

Sie können auch "+" von "+ =", - von "- =" usw. generieren (siehe die vollständige Liste hier ).

C ++ 0x hat einen Vorschlag für Standardfunktionen, also könnte man sagen: default operator==; Wir haben gelernt, dass es hilft, diese Dinge explizit zu machen.

Nur eine Anmerkung, die auch vom Compiler kostenlos zur Verfügung gestellt wird:

  • Betreiber neu
  • Betreiber neu []
  • Betreiber löschen
  • Betreiber löschen []

Konzeptionell ist es nicht einfach, Gleichheit zu definieren. Selbst für POD-Daten könnte man argumentieren, dass selbst wenn die Felder gleich sind, aber ein anderes Objekt (an einer anderen Adresse), es nicht notwendigerweise gleich ist. Dies hängt tatsächlich von der Verwendung des Bedieners ab. Leider ist Ihr Compiler nicht psychisch und kann nicht darauf schließen.

Außerdem sind Standardfunktionen ausgezeichnete Möglichkeiten, sich in den Fuß zu schießen. Die von Ihnen beschriebenen Standardwerte dienen hauptsächlich der Kompatibilität mit POD-Strukturen. Sie verursachen jedoch mehr als genug Chaos, wenn Entwickler sie vergessen oder die Semantik der Standardimplementierungen.

C ++ 20 bietet eine Möglichkeit, einen Standardvergleichsoperator einfach zu implementieren.

Beispiel von cppreference.com :

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

Ich stimme zu, dass für POD-classn der Compiler das für Sie tun könnte. Was Sie jedoch für einfach halten, könnte der Compiler falsch geraten. Also ist es besser, den Programmierer das machen zu lassen.

Ich hatte einmal einen POD-Fall, bei dem zwei der Felder einzigartig waren – ein Vergleich würde nie als richtig angesehen werden. Aber der Vergleich, den ich brauchte, wurde nur auf der Nutzlast verglichen – etwas, das der Compiler nie verstehen würde oder jemals selbst herausfinden könnte.

Außerdem – sie brauchen nicht lange zu schreiben, oder?

Gibt es einen guten Grund dafür? Warum wäre der Vergleich von Mitglied zu Mitglied ein Problem?

Dies mag funktional kein Problem sein, aber in Bezug auf die performance ist der standardmäßige Vergleich von Mitglied zu Mitglied wahrscheinlich suboptimaler als die standardmäßige Zuordnung / Kopie von Mitglied zu Mitglied. Im Gegensatz zur Reihenfolge der Zuweisung wirkt sich die Reihenfolge des Vergleichs auf die performance aus, da das erste ungleiche Element den Rest impliziert, der übersprungen werden kann. Wenn also einige Elemente in der Regel gleich sind, wollen Sie sie zuletzt vergleichen, und der Compiler weiß nicht, welche Elemente mit höherer Wahrscheinlichkeit gleich sind.

Betrachten Sie dieses Beispiel, in dem verboseDescription eine lange Zeichenfolge ist, die aus einer relativ kleinen Menge möglicher Wetterbeschreibungen ausgewählt wird.

 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 } } 

(Natürlich wäre der Compiler berechtigt, die Reihenfolge der Vergleiche zu ignorieren, wenn er erkennt, dass sie keine Nebenwirkungen haben, aber vermutlich würde er seine que aus dem Quellcode nehmen, wo er selbst keine besseren Informationen hat.)

Die Standardvergleichsoperatoren würden eine verschwindend kleine Menge der Zeit korrigieren; Ich erwarte, dass sie eher eine Quelle von Problemen als etwas Nützliches sein würden.

Außerdem sind die von Ihnen genannten Standardmethoden oft unerwünscht. Es ist sehr üblich, Code wie diesen zu sehen, der den Standard-Kopierkonstruktor und operator = loswird:

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

In einer Menge Code ist es üblich, einen Kommentar zu sehen: “Standard-Copy-Konstruktor und -Operator = OK”, um anzuzeigen, dass es kein Fehler ist, dass sie entfernt oder explizit definiert wurden.