Beim Vergleichen von Strukturen in C ++ wurde kein == Operator gefunden

96

Beim Vergleich von zwei Instanzen der folgenden Struktur wird eine Fehlermeldung angezeigt:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

Der Fehler ist:

Fehler C2678: binär '==': Es wurde kein Operator gefunden, der einen linken Operanden vom Typ 'myproj :: MyStruct1' verwendet (oder es gibt keine akzeptable Konvertierung).

Warum?

Jonathan
quelle

Antworten:

125

In C ++ structwird standardmäßig kein Vergleichsoperator generiert. Sie müssen Ihre eigenen schreiben:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
quelle
21
@ Jonathan: Warum sollte C ++ wissen, wie Sie Ihre structs auf Gleichheit vergleichen möchten ? Und wenn Sie den einfachen Weg wollen, gibt es immer memcmpso lange, bis Ihre Strukturen keinen Zeiger enthalten.
Xeo
12
@Xeo: memcmpschlägt mit Nicht-POD-Mitgliedern (wie std::string) und gepolsterten Strukturen fehl .
Fredoverflow
16
@ Jonathan Die "modernen" Sprachen, die ich kenne, bieten einem ==Operator eine Semantik, die fast nie das ist, was gewünscht wird. (Und sie bieten keine Möglichkeit, sie zu überschreiben, sodass Sie am Ende eine Mitgliedsfunktion verwenden müssen.) Die "modernen" Sprachen, die ich kenne, bieten auch keine Wertesemantik, sodass Sie gezwungen sind, Zeiger zu verwenden, auch wenn sie nicht geeignet sind.
James Kanze
4
@ Jonathan Fälle variieren definitiv, sogar innerhalb eines bestimmten Programms. Für Entitätsobjekte funktioniert die von Java bereitgestellte Lösung sehr gut (und natürlich können Sie in C ++ genau dasselbe tun - es ist sogar idiomatisches C ++ für Entitätsobjekte). Die Frage ist, was mit Wertobjekten zu tun ist. C ++ bietet operator=aus Gründen der C-Kompatibilität eine Standardeinstellung (auch wenn dies häufig falsch ist). Für die C-Kompatibilität ist jedoch keine erforderlich operator==. Global bevorzuge ich, was C ++ macht, was Java macht. (Ich kenne C # nicht, also ist das vielleicht besser.)
James Kanze
9
Zumindest sollte es möglich = defaultsein!
user362515
93

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 intElements, 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 unionzu 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::tupleVergleichsoperatoren zu nutzen, um Ihre eigenen Klasseninstanzen zu vergleichen. Verwenden Sie std::tiediese 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 returnAnweisung 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 MyStruct1aus erstellen my_myStruct2und dann den Vergleich durchführen. Dies würde definitiv MyStruct1::an_intauf den Standardparameterwert des Konstruktors von gesetzt bleiben -1. Je nachdem , ob Sie umfassen an_intVergleich in der Umsetzung Ihrer operator==, eine MyStruct1Kraft oder vielleicht vergleichen nicht gleich ein , MyStruct2dass selbst vergleicht gleich das MyStruct1‚s my_struct_2Mitglied! Darüber hinaus kann das Erstellen eines temporären MyStruct1Elements eine sehr ineffiziente Operation sein, da das vorhandene my_struct2Mitglied in ein temporäres Element kopiert wird , um es nach dem Vergleich wegzuwerfen. (Natürlich können Sie diese implizite Konstruktion von MyStruct1s zum Vergleich verhindern, indem Sie diesen Konstruktor expliciterstellen oder den Standardwert für entfernen an_int.)

Implementierung der Mitglieder

Wenn Sie die implizite Konstruktion von a MyStruct1aus 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 constSchlü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 constObjekte 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 stringschmerzhaft 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.

Tony Delroy
quelle
Nun, eigentlich sollte für die Vergleichsoperatoren <,>, <=,> = nur die Implementierung von <erforderlich sein. Der Rest folgt, und es gibt keine sinnvolle Möglichkeit, sie zu implementieren, was etwas anderes bedeutet als die Implementierung, die automatisch generiert werden kann. Es ist bizarr, dass Sie sie alle selbst implementieren müssen.
André
@ André: häufiger eine manuell geschrieben int cmp(x, y)oder compareFunktion einen negativen Wert für die Rückführung x < y, 0 für Gleichberechtigung und einen positiven Wert für x > ydie Basis verwendet wird <, >, <=, >=, ==, und !=; Es ist sehr einfach, CRTP zu verwenden, um all diese Operatoren in eine Klasse einzufügen. Ich bin sicher, dass ich die Implementierung in einer alten Antwort veröffentlicht habe, sie aber nicht schnell finden konnte.
Tony Delroy
@ TonyD Sicher können Sie das tun, aber es ist genauso einfach zu implementieren >, <=und >=in Bezug auf <. Sie könnten auch implementieren ==und !=auf diese Weise, aber das würde in der Regel nicht eine sehr effiziente Implementierung Ich denke , sein. Es wäre schön, wenn für all dies kein CRTP oder andere Tricks erforderlich wären, aber der Standard würde nur die automatische Generierung dieser Operatoren vorschreiben, wenn dies nicht explizit vom Benutzer definiert wird und <definiert ist.
André
@ André: es ist da ==und !=nicht effizient mit ausgedrückt werden könnte , <dass für alles , was üblich ist zu vergleichen , verwenden. „Es wäre schön, wenn kein CRTP oder andere Tricks benötigt würden“ - vielleicht, aber dann kann CRTP leicht verwendet werden , viele andere Betreiber zu generieren (zB bitweise |, &, ^aus |=, &=und ^=, + - * / %aus deren Zuordnung Formen; binäre -von einstellige Negation und +) - so viele potenziell nützliche Variationen dieses Themas, dass es nicht besonders elegant ist, nur eine Sprachfunktion für ein ziemlich beliebiges Stück davon bereitzustellen.
Tony Delroy
Würde es Ihnen etwas ausmachen, einer plausiblen Implementierung eine Version hinzuzufügen, mit std::tieder mehrere Mitglieder verglichen werden können?
NathanOliver
17

Sie müssen explizit operator ==für definieren MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Jetzt ist der == Vergleich für 2 solcher Objekte zulässig.

iammilind
quelle
11

Beginnend in C ++ 20, sollte es möglich sein , einen vollständigen Satz von Standardvergleichsoperatoren hinzufügen ( ==, <=usw.) zu einer Klasse von einer Erklärung der Standard - Drei-Wege - Vergleichsoperator ( „Raumschiff“ Operator), wie folgt aus :

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Bei einem kompatiblen C ++ 20-Compiler kann das Hinzufügen dieser Zeile zu MyStruct1 und MyStruct2 ausreichen, um Gleichheitsvergleiche zu ermöglichen, vorausgesetzt, die Definition von MyStruct2 ist kompatibel.

Joe Lee
quelle
2

Der Vergleich funktioniert nicht bei Strukturen in C oder C ++. Vergleichen Sie stattdessen nach Feldern.

Rafe Kettler
quelle
2

Standardmäßig haben Strukturen keinen ==Operator. Sie müssen Ihre eigene Implementierung schreiben:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
quelle
0

Standardmäßig funktioniert der Operator == nur für Grundelemente. Damit Ihr Code funktioniert, müssen Sie den Operator == für Ihre Struktur überladen.

Babak Naffas
quelle
0

Weil Sie keinen Vergleichsoperator für Ihre Struktur geschrieben haben. Der Compiler generiert es nicht für Sie. Wenn Sie also einen Vergleich wünschen, müssen Sie es selbst schreiben.


quelle