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
int
elementu 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
union
do 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 MyStruct2
zbyt.
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::tuple
operatory porównania, aby porównać własne instancje klas - po prostu użyj ich std::tie
do 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 return
instrukcji, 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 MyStruct1
from my_myStruct2
, a następnie wykonując porównanie. To na pewno pozostawiłoby MyStruct1::an_int
ustawienie domyślnej wartości parametru konstruktora wynoszącej -1
. W zależności od tego, czy uwzględnisz an_int
porównanie w implementacji your operator==
, porównanie MyStruct1
może, ale nie musi, równe a, MyStruct2
które samo jest równe elementowi MyStruct1
's my_struct_2
! Ponadto utworzenie tymczasowego MyStruct1
może być bardzo nieefektywną operacją, ponieważ wymaga skopiowania istniejącego my_struct2
elementu do tymczasowego, tylko po to, aby go wyrzucić po porównaniu. (Oczywiście można zapobiec tej niejawnej konstrukcji MyStruct1
s dla porównania, tworząc ten konstruktor explicit
lub usuwając domyślną wartość for an_int
.)
Implementacja członka
Jeśli chcesz uniknąć niejawnej konstrukcji a MyStruct1
z 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 const
sł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 const
obiektach.
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 string
boleś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.
struct
kątem równości? A jeśli chcesz prostego sposobu, zawsze jestmemcmp
tak długo, że twoje struktury nie zawierają wskaźnika.