Nie znaleziono operatora == podczas porównywania struktur w C ++

96

Porównując dwa wystąpienia następującej struktury, otrzymuję błąd:

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

Błąd:

błąd C2678: binarny '==': nie znaleziono operatora, który przyjmuje lewostronny operand typu 'myproj :: MyStruct1' (lub nie ma akceptowalnej konwersji)

Czemu?

Jonathan
źródło

Odpowiedzi:

126

W C ++ structs nie mają domyślnie generowanego operatora porównania. Musisz napisać własne:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
źródło
21
@Jonathan: Dlaczego C ++ miałby wiedzieć, jak chcesz porównać swoje wyniki pod structkątem równości? A jeśli chcesz prostego sposobu, zawsze jest memcmptak długo, że twoje struktury nie zawierają wskaźnika.
Xeo,
12
@Xeo: memcmpkończy się niepowodzeniem z elementami niebędącymi POD (takimi jak std::string) i wyściełanymi strukturami.
fredoverflow,
16
@Jonathan „Współczesne” języki, które znam, dostarczają ==operatorowi - semantyki, która prawie nigdy nie jest tym, czego szukamy . (I nie zapewniają możliwości zastąpienia go, więc w końcu musisz użyć funkcji członkowskiej). Znane mi języki „nowoczesne” również nie zapewniają semantyki wartości, więc jesteś zmuszony używać wskaźników, nawet jeśli nie są one odpowiednie.
James Kanze,
4
@Jonathan Przypadki zdecydowanie się różnią, nawet w ramach danego programu. W przypadku obiektów encji rozwiązanie udostępniane przez Javę działa bardzo dobrze (i oczywiście można zrobić dokładnie to samo w C ++ - jest to nawet idiomatyczny C ++ dla obiektów encji). Pytanie brzmi, co zrobić z przedmiotami wartości. C ++ udostępnia wartość domyślną operator=(nawet jeśli często robi źle) ze względu na zgodność z C. Zgodność z C nie wymaga operator==jednak. Ogólnie wolę to, co robi C ++, od tego, co robi Java. (Nie znam C #, więc może to lepiej.)
James Kanze
9
Przynajmniej powinno to być możliwe = default!
user362515
94

C ++ 20 wprowadził domyślne porównania, zwane także „statkiem kosmicznym”operator<=> , które pozwalają na żądanie operatorów wygenerowanych przez kompilator </ <=/ ==/ !=/ >=/ i / lub >z oczywistą / naiwną (?) Implementacją ...

auto operator<=>(const MyClass&) const = default;

... ale możesz to dostosować do bardziej skomplikowanych sytuacji (omówionych poniżej). Zobacz tutaj propozycję językową, która zawiera uzasadnienia i dyskusję. Ta odpowiedź pozostaje aktualna dla C ++ 17 i wcześniejszych oraz dla wglądu w to, kiedy należy dostosować implementację operator<=>....

Może wydawać się trochę nieprzydatne dla C ++, jeśli wcześniej tego nie ujednolicono, ale często struktury / klasy mają pewne elementy składowe danych do wykluczenia z porównania (np. Liczniki, wyniki w pamięci podręcznej, pojemność kontenera, kod powodzenia / błędu ostatniej operacji, kursory), ponieważ a także decyzje dotyczące niezliczonych rzeczy, w tym między innymi:

  • które pola porównać najpierw, np. porównanie konkretnego intelementu członkowskiego może bardzo szybko wyeliminować 99% nierównych obiektów, podczas gdy element map<string,string>członkowski może często mieć identyczne wpisy i być stosunkowo drogi do porównania - jeśli wartości są ładowane w czasie wykonywania, programista może mieć wgląd w kompilator nie może
  • przy porównywaniu ciągów znaków: uwzględnianie wielkości liter, równoważność białych znaków i separatorów, unikanie konwencji ...
  • precyzja przy porównywaniu liczb zmiennoprzecinkowych / podwójnych
  • czy wartości zmiennoprzecinkowe NaN powinny być uważane za równe
  • porównywanie wskaźników lub wskazywane na dane (a jeśli to drugie, jak wiedzieć, czy wskaźniki są do tablic i ile obiektów / bajtów wymaga porównania)
  • czy sprawach porządkowych przy porównywaniu nieposortowane pojemników (np vector, list), a jeśli tak, to czy to jest ok, aby posortować je na miejscu przed porównaniem porównaniu z użyciem dodatkowej pamięci do sortowania tymczasowych za każdym razem odbywa się porównanie
  • ile elementów tablicy zawiera obecnie prawidłowe wartości, które należy porównać (czy jest gdzieś rozmiar lub wartownik?)
  • który element a uniondo porównania
  • normalizacja: na przykład typy dat mogą zezwalać na dzień miesiąca lub miesiąc poza zakresem, lub obiekt racjonalny / ułamkowy może mieć 6/8, podczas gdy inny ma 3/4, które ze względu na wydajność korygują leniwie z osobnym krokiem normalizacji; być może będziesz musiał zdecydować, czy wyzwolić normalizację przed porównaniem
  • co zrobić, gdy słabe punkty nie są prawidłowe
  • jak obsługiwać członków i bazy, które same się nie implementują operator==(ale mogą mieć compare()lub operator<lub str()lub pobierające ...)
  • jakie blokady należy przyjąć podczas odczytywania / porównywania danych, które inne wątki mogą chcieć zaktualizować

Więc miło jest mieć błąd, dopóki nie zastanowisz się wyraźnie, co powinno oznaczać porównanie dla twojej konkretnej struktury, zamiast pozwolić mu się skompilować, ale nie dać ci znaczącego wyniku w czasie wykonywania .

Wszystko to powiedziawszy, byłoby dobrze, gdyby C ++ pozwolił ci powiedzieć, bool operator==() const = default;kiedy zdecydowałeś, że „naiwny” ==test członka po członku jest w porządku. To samo dotyczy !=. Podane wielu członków / zasady, „default” <, <=, >, i >=implementacje wydają się beznadziejne chociaż - kaskadowych na podstawie kolejności deklaracji jest możliwe, ale bardzo mało prawdopodobne, aby to, co chciał, biorąc pod uwagę sprzeczne imperatywy dla państw zamawiającego (podstawy bycia koniecznie przed członkami, grupowanie przez dostępność, budowa / zniszczenie przed zależnym użyciem). Aby być bardziej użytecznym, C ++ potrzebowałby nowego systemu adnotacji składowych / bazowych danych, który kierowałby wyborami - byłoby to jednak świetne rozwiązanie w standardzie, idealnie w połączeniu z generowaniem kodu zdefiniowanego przez użytkownika w oparciu o AST ... Oczekuję to'

Typowa implementacja operatorów równości

Wiarygodna implementacja

Jest prawdopodobne , że rozsądne i efektywne wdrożenie będzie:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Zauważ, że to należy przedsięwziąć operator==dla MyStruct2zbyt.

Implikacje tej implementacji i alternatywy są omówione poniżej w części Omówienie szczegółów dotyczących Twojego MyStruct1 .

Spójne podejście do ==, <,> <= itd

Łatwo jest wykorzystać std::tupleoperatory porównania, aby porównać własne instancje klas - po prostu użyj ich std::tiedo utworzenia krotek odwołań do pól w żądanej kolejności porównania. Uogólniam mój przykład stąd :

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

Kiedy „posiadasz” (tj. Możesz edytować, czynnik z bibliotekami firmowymi i zewnętrznymi) klasę, którą chcesz porównać, a zwłaszcza z gotowością C ++ 14 do wywnioskowania typu zwracanego funkcji z returninstrukcji, często przyjemniej jest dodać „ powiąż funkcję składową z klasą, którą chcesz porównać:

auto tie() const { return std::tie(my_struct1, an_int); }

Następnie powyższe porównania upraszczają się do:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Jeśli chcesz mieć pełniejszy zestaw operatorów porównania, proponuję operatory wzmocnienia (szukaj less_than_comparable). Jeśli z jakiegoś powodu jest to nieodpowiednie, pomysł obsługi makr (online) może ci się spodobać lub nie :

#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)

... które można następnie wykorzystać a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(C ++ 14 wersja z powiązanymi członkami tutaj )

Omówienie specyfiki Twojego MyStruct1

Istnieją konsekwencje wyboru zapewnienia wolnostojącego kontra członka operator==()...

Realizacja wolnostojąca

Masz interesującą decyzję do podjęcia. Ponieważ twoja klasa może być niejawnie skonstruowana z a MyStruct2, bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)funkcja wolnostojąca / niebędąca składową obsługuje ...

my_MyStruct2 == my_MyStruct1

... tworząc najpierw tymczasowy plik MyStruct1from my_myStruct2, a następnie wykonując porównanie. To na pewno pozostawiłoby MyStruct1::an_intustawienie domyślnej wartości parametru konstruktora wynoszącej -1. W zależności od tego, czy uwzględnisz an_intporównanie w implementacji your operator==, porównanie MyStruct1może, ale nie musi, równe a, MyStruct2które samo jest równe elementowi MyStruct1's my_struct_2! Ponadto utworzenie tymczasowego MyStruct1może być bardzo nieefektywną operacją, ponieważ wymaga skopiowania istniejącego my_struct2elementu do tymczasowego, tylko po to, aby go wyrzucić po porównaniu. (Oczywiście można zapobiec tej niejawnej konstrukcji MyStruct1s dla porównania, tworząc ten konstruktor explicitlub usuwając domyślną wartość for an_int.)

Implementacja członka

Jeśli chcesz uniknąć niejawnej konstrukcji a MyStruct1z a MyStruct2, uczyń z operatora porównania funkcję składową :

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Zwróć uwagę, że constsłowo kluczowe - potrzebne tylko do implementacji składowej - informuje kompilator, że porównywanie obiektów ich nie modyfikuje, więc może być dozwolone na constobiektach.

Porównanie widocznych reprezentacji

Czasami najłatwiejszym sposobem uzyskania takiego porównania jest ...

    return lhs.to_string() == rhs.to_string();

... co często jest też bardzo drogie - te stringboleśnie stworzone, aby je wyrzucić! W przypadku typów z wartościami zmiennoprzecinkowymi porównywanie widocznych reprezentacji oznacza, że ​​liczba wyświetlanych cyfr określa tolerancję, w ramach której prawie równe wartości są traktowane jako równe podczas porównania.

Tony Delroy
źródło
Cóż, właściwie dla operatorów porównania <,>, <=,> = powinno być wymagane tylko zaimplementowanie <. Reszta następuje i nie ma sensownego sposobu ich implementacji, co oznacza coś innego niż implementacja, która może być generowana automatycznie. To dziwne, że musisz je wszystkie wdrożyć samodzielnie.
André
@ André: częściej pisane ręcznie int cmp(x, y)lub comparefunkcja powrocie ujemną wartość x < y, 0 dla równości i dodatnią wartość x > ysłuży jako podstawa do <, >, <=, >=, ==, i !=; bardzo łatwo jest użyć CRTP do wstrzyknięcia wszystkich tych operatorów do klasy. Jestem pewien, że opublikowałem implementację w starej odpowiedzi, ale nie mogłem jej szybko znaleźć.
Tony Delroy
@TonyD Jasne, że możesz to zrobić, ale jest to równie łatwe do wdrożenia >, jak <=i >=pod względem <. Można by też zaimplementować ==i w !=ten sposób, ale myślę, że zazwyczaj nie byłaby to bardzo wydajna implementacja. Byłoby miło, gdyby do tego wszystkiego nie był potrzebny żaden CRTP ani inne sztuczki, ale standard po prostu nakazałby automatyczne generowanie tych operatorów, jeśli nie zostałyby wyraźnie zdefiniowane przez użytkownika i <są zdefiniowane.
André
@ André: to dlatego, że ==i !=może nie być efektywnie wyrażone za pomocą <tego, że użycie porównania do wszystkiego jest powszechne. „Byłoby miło, gdyby nie CRTP lub inne sztuczki byłby potrzebny” - być może, ale wtedy CRTP mogą być łatwo wykorzystane do generowania wiele innych operatorów (np bitowe |, &, ^z |=, &=a ^=, + - * / %od ich formy cesji; binarny -z jednoargumentowego negacji i +) - tak wiele potencjalnie przydatnych odmian tego tematu, że samo zapewnienie funkcji językowej dla jednego dość dowolnego fragmentu tego nie jest szczególnie eleganckie.
Tony Delroy
Czy mógłbyś dodać do wiarygodnej implementacji wersję, która używa std::tiedo porównania wielu członków?
NathanOliver
17

Musisz wyraźnie zdefiniować operator ==dla MyStruct1.

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

Teraz porównanie == jest legalne dla 2 takich obiektów.

iammilind
źródło
11

Zaczynając w C ++ 20, powinno być możliwe, aby dodać pełny zestaw operatorów porównania (domyślne ==, <=etc.) do klasy deklarując domyślny trójdrożny operator porównania ( „statek kosmiczny” operator), jak poniżej:

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

W przypadku zgodnego kompilatora C ++ 20 dodanie tego wiersza do MyStruct1 i MyStruct2 może wystarczyć, aby umożliwić porównania równości, zakładając, że definicja MyStruct2 jest zgodna.

Joe Lee
źródło
2

Porównanie nie działa na strukturach w C lub C ++. Zamiast tego porównaj według pól.

Rafe Kettler
źródło
2

Domyślnie struktury nie mają ==operatora. Będziesz musiał napisać własną implementację:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
źródło
0

Po wyjęciu z pudełka operator == działa tylko dla prymitywów. Aby kod działał, musisz przeciążyć operator == dla swojej struktury.

Babak Naffas
źródło
0

Ponieważ nie napisałeś operatora porównania dla swojej struktury. Kompilator nie generuje tego dla Ciebie, więc jeśli chcesz porównania, musisz napisać to sam.


źródło