In C ++ 20 wurden Standardvergleicheoperator<=>
eingeführt , auch bekannt als "Raumschiff" , mit denen Sie vom Compiler generierte <
/ <=
/ ==
/ !=
/ >=
/ und / oder >
Operatoren mit der offensichtlichen / naiven (?) Implementierung anfordern können ...
auto operator<=>(const MyClass&) const = default;
... aber Sie können dies für kompliziertere Situationen anpassen (siehe unten). Siehe hier für die Sprache Vorschlag, die Rechtfertigungen und Diskussion enthält. Diese Antwort bleibt für C ++ 17 und frühere Versionen relevant und gibt Aufschluss darüber, wann Sie die Implementierung von operator<=>
.... anpassen sollten .
Es mag für C ++ etwas wenig hilfreich erscheinen, dies nicht bereits früher standardisiert zu haben, aber häufig müssen Strukturen / Klassen einige Datenelemente vom Vergleich ausschließen (z. B. Zähler, zwischengespeicherte Ergebnisse, Containerkapazität, Erfolg / Fehlercode der letzten Operation, Cursor) sowie Entscheidungen über unzählige Dinge zu treffen, einschließlich, aber nicht beschränkt auf:
- Welche Felder zuerst verglichen werden sollen, z. B. das Vergleichen eines bestimmten
int
Elements, kann 99% der ungleichen Objekte sehr schnell beseitigen, während ein map<string,string>
Mitglied häufig identische Einträge hat und relativ teuer zu vergleichen ist. Wenn die Werte zur Laufzeit geladen werden, hat der Programmierer möglicherweise Einblicke in die Compiler kann unmöglich
- beim Vergleichen von Zeichenfolgen: Groß- und Kleinschreibung, Äquivalenz von Leerzeichen und Trennzeichen, Konventionen entkommen ...
- Präzision beim Vergleich von Floats / Doubles
- ob NaN-Gleitkommawerte als gleich angesehen werden sollten
- Vergleichen von Zeigern oder Zeigen auf Daten (und wenn letzteres, wie man weiß, ob die Zeiger auf Arrays sind und wie viele Objekte / Bytes verglichen werden müssen)
- ob um Angelegenheiten beim Vergleich unsortiert Behälter (zB
vector
, list
), und wenn ja , ob es in Ordnung ist , sie an Ort und Stelle zu sortieren , bevor Vergleich vs. mit zusätzlichen Speichern zu sortieren Provisorien jedesmal , wenn ein Vergleich gemacht wird
- Wie viele Array-Elemente enthalten derzeit gültige Werte, die verglichen werden sollten (gibt es irgendwo eine Größe oder einen Sentinel?)
- welches Mitglied von a
union
zu vergleichen
- Normalisierung: Zum Beispiel können Datumsarten einen Tag außerhalb des Bereichs oder einen Monat außerhalb des Bereichs zulassen, oder ein rationales / Bruchteil-Objekt kann 6/8 haben, während ein anderes 3/4er hat, die aus Leistungsgründen korrigiert werden träge mit einem separaten Normalisierungsschritt; Möglicherweise müssen Sie vor dem Vergleich entscheiden, ob eine Normalisierung ausgelöst werden soll
- Was tun, wenn schwache Zeiger nicht gültig sind?
- wie man mit Mitgliedern und Basen umgeht, die sich nicht
operator==
selbst implementieren (aber möglicherweise compare()
oder operator<
oder str()
oder Getter haben ...)
- Welche Sperren müssen beim Lesen / Vergleichen von Daten genommen werden, die andere Threads möglicherweise aktualisieren möchten?
Es ist also nett, einen Fehler zu haben, bis Sie explizit darüber nachgedacht haben, was ein Vergleich für Ihre spezifische Struktur bedeuten soll, anstatt ihn kompilieren zu lassen, aber zur Laufzeit kein aussagekräftiges Ergebnis zu erzielen .
Alles in allem wäre es gut, wenn C ++ Sie sagen bool operator==() const = default;
lassen würde, wenn Sie entschieden hätten, dass ein "naiver" ==
Test von Mitglied zu Mitglied in Ordnung ist. Gleiches gilt für !=
. Gegeben mehrere Mitglieder / Basen, „default“ <
, <=
, >
und >=
Implementierungen scheinen hoffnungslos obwohl - Kaskadierung auf der Grundlage der Reihenfolge der Deklaration ist möglich , aber sehr unwahrscheinlich zu sein , was gewünscht ist, da widersprüchliche Erfordernisse für das Mitglied Ordnung (Basen sind unbedingt vor Mitgliedern, die Gruppierung von Zugänglichkeit, Bau / Zerstörung vor abhängiger Nutzung). Um allgemeiner nützlich zu sein, würde C ++ ein neues Annotationssystem für Datenelemente / Basen benötigen, um die Auswahl zu leiten - das wäre jedoch eine großartige Sache im Standard, idealerweise in Verbindung mit einer AST-basierten benutzerdefinierten Codegenerierung ... Ich erwarte es'
Typische Implementierung von Gleichstellungsoperatoren
Eine plausible Umsetzung
Es ist wahrscheinlich, dass eine vernünftige und effiziente Implementierung wäre:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.my_struct2 == rhs.my_struct2 &&
lhs.an_int == rhs.an_int;
}
Beachten Sie, dass dies auch ein operator==
für benötigt MyStruct2
.
Die Auswirkungen dieser Implementierung und Alternativen werden unter der Überschrift Diskussion der Besonderheiten Ihres MyStruct1 weiter unten erläutert .
Ein konsistenter Ansatz für ==, <,> <= etc.
Es ist einfach, die std::tuple
Vergleichsoperatoren zu nutzen, um Ihre eigenen Klasseninstanzen zu vergleichen. Verwenden Sie std::tie
diese Option, um Tupel von Verweisen auf Felder in der gewünschten Vergleichsreihenfolge zu erstellen. Verallgemeinern Sie mein Beispiel von hier :
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...
Wenn Sie die Klasse, die Sie vergleichen möchten, "besitzen" (dh bearbeiten können, einen Faktor mit Unternehmens- und Drittanbieter-Bibliotheken), und insbesondere mit der Bereitschaft von C ++ 14, den Funktionsrückgabetyp aus der return
Anweisung abzuleiten , ist es oft besser, eine " Binden Sie die Member-Funktion an die Klasse, die Sie vergleichen möchten:
auto tie() const { return std::tie(my_struct1, an_int); }
Dann vereinfachen sich die obigen Vergleiche zu:
inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
return lhs.tie() == rhs.tie();
}
Wenn Sie einen umfassenderen Satz von Vergleichsoperatoren wünschen, empfehle ich Boost-Operatoren (Suche nach less_than_comparable
). Wenn es aus irgendeinem Grund ungeeignet ist, kann Ihnen die Idee der Unterstützung von Makros (online) gefallen oder auch nicht :
#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)
... das kann dann a la verwendet werden ...
#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)
(C ++ 14 Member-Tie-Version hier )
Diskussion der Besonderheiten Ihres MyStruct1
Die Entscheidung, ein freistehendes Mitglied zu stellen, hat Auswirkungen operator==()
...
Freistehende Implementierung
Sie müssen eine interessante Entscheidung treffen. Da Ihre Klasse implizit aus a aufgebaut werden kann MyStruct2
, würde eine freistehende / Nichtmitgliedsfunktion bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)
...
my_MyStruct2 == my_MyStruct1
... indem Sie zuerst ein temporäres MyStruct1
aus erstellen my_myStruct2
und dann den Vergleich durchführen. Dies würde definitiv MyStruct1::an_int
auf den Standardparameterwert des Konstruktors von gesetzt bleiben -1
. Je nachdem , ob Sie umfassen an_int
Vergleich in der Umsetzung Ihrer operator==
, eine MyStruct1
Kraft oder vielleicht vergleichen nicht gleich ein , MyStruct2
dass selbst vergleicht gleich das MyStruct1
‚s my_struct_2
Mitglied! Darüber hinaus kann das Erstellen eines temporären MyStruct1
Elements eine sehr ineffiziente Operation sein, da das vorhandene my_struct2
Mitglied in ein temporäres Element kopiert wird , um es nach dem Vergleich wegzuwerfen. (Natürlich können Sie diese implizite Konstruktion von MyStruct1
s zum Vergleich verhindern, indem Sie diesen Konstruktor explicit
erstellen oder den Standardwert für entfernen an_int
.)
Implementierung der Mitglieder
Wenn Sie die implizite Konstruktion von a MyStruct1
aus a vermeiden möchten MyStruct2
, machen Sie den Vergleichsoperator zu einer Elementfunktion:
struct MyStruct1
{
...
bool operator==(const MyStruct1& rhs) const
{
return tie() == rhs.tie(); // or another approach as above
}
};
Beachten Sie, dass das const
Schlüsselwort - das nur für die Member-Implementierung benötigt wird - den Compiler darauf hinweist, dass das Vergleichen von Objekten diese nicht ändert und daher für const
Objekte zulässig sein kann.
Vergleich der sichtbaren Darstellungen
Manchmal ist der einfachste Weg, um die Art von Vergleich zu erhalten, die Sie wollen, ...
return lhs.to_string() == rhs.to_string();
... was oft auch sehr teuer ist - die sind string
schmerzhaft geschaffen, nur um weggeworfen zu werden! Bei Typen mit Gleitkommawerten bedeutet der Vergleich sichtbarer Darstellungen, dass die Anzahl der angezeigten Ziffern die Toleranz bestimmt, innerhalb derer nahezu gleiche Werte beim Vergleich als gleich behandelt werden.
struct
s auf Gleichheit vergleichen möchten ? Und wenn Sie den einfachen Weg wollen, gibt es immermemcmp
so lange, bis Ihre Strukturen keinen Zeiger enthalten.