Uczę się o przeciążenia operatora w C ++, i widzę, że ==
i !=
są po prostu pewne specjalne funkcje, które mogą być dostosowane do typów zdefiniowanych przez użytkownika. Martwię się jednak, dlaczego potrzebne są dwie osobne definicje? Myślałem, że jeśli a == b
to prawda, a != b
to automatycznie jest fałszem i odwrotnie, i nie ma innej możliwości, ponieważ z definicji tak a != b
jest !(a == b)
. I nie wyobrażałem sobie żadnej sytuacji, w której to nie byłoby prawdą. Ale może moja wyobraźnia jest ograniczona, czy czegoś nie wiem?
Wiem, że mogę zdefiniować jedno w kategoriach drugiego, ale nie o to pytam. Nie pytam też o rozróżnienie między porównywaniem obiektów według wartości lub tożsamości. Lub czy dwa obiekty mogą być równe i nierównomierne jednocześnie (to zdecydowanie nie jest opcja! Te rzeczy wzajemnie się wykluczają). Pytam o to:
Czy jest jakakolwiek sytuacja, w której zadawanie pytań o równość dwóch przedmiotów ma sens, ale pytanie o nierówność między nimi nie ma sensu? (z perspektywy użytkownika lub z perspektywy implementatora)
Jeśli nie ma takiej możliwości, to dlaczego na Ziemi C ++ mają te dwa operatory zdefiniowane jako dwie odrębne funkcje?
źródło
'undefined' != expression
zawsze jest to prawda (lub fałsz, lub niezdefiniowane), niezależnie od tego, czy wyrażenie może być ocenione. W takim przypadkua!=b
zwróci prawidłowy wynik zgodnie z definicją, ale!(a==b)
nie powiedzie się, jeśli nie będzieb
można go ocenić. (Lub poświęć dużo czasu, jeśli ocenab
jest droga).(NaN != NaN) == true
Odpowiedzi:
Byś nie chcą językiem automatycznie przepisać
a != b
jako!(a == b)
kiedya == b
powróci coś innego niżbool
. Istnieje kilka powodów, dla których możesz to zrobić.Możesz mieć obiekty konstruktora wyrażeń, w których
a == b
nie ma i nie ma na celu przeprowadzania żadnego porównania, ale po prostu buduje jakiś reprezentujący węzeł wyrażeniaa == b
.Możesz mieć leniwą ocenę, w której
a == b
nie ma i nie ma na celu bezpośredniego porównania, ale zamiast tego zwraca pewien rodzaj tego,lazy<bool>
który można później przekształcić wbool
sposób niejawny lub jawny, aby faktycznie wykonać porównanie. Prawdopodobnie w połączeniu z obiektami konstruktora wyrażeń, aby umożliwić pełną optymalizację wyrażeń przed oceną.Możesz mieć niestandardową
optional<T>
klasę szablonów, w której podane są opcjonalne zmiennet
iu
chcesz zezwolićt == u
, ale spraw , aby powróciłaoptional<bool>
.Jest chyba coś więcej, o czym nie myślałem. I chociaż w tych przykładach operacja
a == b
ia != b
oba mają sens, nadala != b
nie jest tym samym!(a == b)
, więc potrzebne są osobne definicje.źródło
!=
zamiast dwóch przejściach obliczeniowych==
potem!
. Zwłaszcza w czasach, w których nie można było polegać na kompilatorze łączącym pętle. Lub nawet dzisiaj, jeśli nie uda ci się przekonać kompilatora, twoje wektory się nie pokrywają.!
może także zbudować jakiś węzeł ekspresji i jesteśmy nadal w porządku wymianiea != b
z!(a == b)
tak dalece, jak to idzie. To samo dotyczylazy<bool>::operator!
, może wrócićlazy<bool>
.optional<bool>
jest bardziej przekonujący, ponieważ logiczna prawdomównośćboost::optional
zależy na przykład od tego, czy wartość istnieje, a nie od samej wartości.Nan
s - proszę pamiętać oNaN
s;Ponieważ możesz je przeciążyć, a przeciążając je, możesz nadać im zupełnie inne znaczenie niż ich oryginalne.
Weźmy na przykład operator
<<
, pierwotnie bitowy operator przesunięcia w lewo, obecnie często przeciążony jako operator wstawiania, jak wstd::cout << something
; zupełnie inne znaczenie niż oryginalne.Jeśli więc zaakceptujesz, że znaczenie operatora zmienia się, gdy go przeciążasz, nie ma powodu, aby uniemożliwić użytkownikowi nadanie operatorowi znaczenia,
==
które nie jest dokładnie zaprzeczeniem operatora!=
, choć może to być mylące.źródło
==
i!=
istnieć jako odrębne podmioty. Z drugiej strony prawdopodobnie nie istnieją one jako odrębne operatory, ponieważ można je przeciążać osobno, ale ze względu na starszą i wygodę (zwięzłość kodu).Nie musisz definiować obu.
Jeśli wzajemnie się wykluczają, nadal możesz być zwięzły, definiując tylko
==
i<
obok std :: rel_opsZ preferencji:
Często kojarzymy tych operatorów z równością.
Chociaż tak zachowują się na podstawowych typach, nie ma obowiązku, aby takie było ich zachowanie na niestandardowych typach danych. Nie musisz nawet zwracać bool, jeśli nie chcesz.
Widziałem ludzi przeciążających operatorów w dziwny sposób, ale okazało się, że ma to sens w przypadku ich aplikacji. Nawet jeśli interfejs wydaje się wskazywać, że wzajemnie się wykluczają, autor może chcieć dodać określoną logikę wewnętrzną.
Wiem, że chcesz konkretnego przykładu,
więc oto jeden z frameworka Catch testing, który moim zdaniem był praktyczny:
Operatorzy ci robią różne rzeczy i nie ma sensu definiować jednej metody jako! (Nie) drugiej. Powodem tego jest to, że środowisko może wydrukować wykonane porównanie. W tym celu musi uchwycić kontekst użytego przeciążonego operatora.
źródło
std::rel_ops
? Dziękuję bardzo za zwrócenie na to uwagi.rel_ops
i tak jest okropny.Istnieje kilka bardzo dobrze ustalone konwencje, które
(a == b)
i(a != b)
sązarówno fałszyweniekoniecznie przeciwieństwa. W szczególności w SQL każde porównanie z NULL daje NULL, nie jest prawdą ani fałszem.Prawdopodobnie nie jest dobrym pomysłem tworzenie nowych przykładów, jeśli to w ogóle możliwe, ponieważ jest to tak mało intuicyjne, ale jeśli próbujesz modelować istniejącą konwencję, fajnie jest mieć opcję, aby Twoi operatorzy zachowywali się „poprawnie” w tym celu kontekst.
źródło
NULL == something
zwrócić Nieznanego, a także chciałbyśNULL != something
zwrócić Nieznany i chciałbyś!Unknown
zwrócićUnknown
. I w takim przypadku implementacjaoperator!=
jako negacjaoperator==
jest nadal poprawna.operator==
albooperator!=
nie drugiego, albo 2) wdrożenieoperator!=
w sposób inny niż negacjaoperator==
. A implementacja logiki SQL dla wartości NULL nie jest tego przypadkiem.Odpowiem tylko na drugą część twojego pytania, a mianowicie:
Jednym z powodów, dla których warto pozwolić programistom na przeciążenie obu, jest wydajność. Możesz zezwolić na optymalizacje, wdrażając zarówno
==
i!=
. Wtedyx != y
może być tańszy niż!(x == y)
jest. Niektóre kompilatory mogą być w stanie zoptymalizować go dla Ciebie, ale być może nie, szczególnie jeśli masz złożone obiekty z dużą ilością rozgałęzień.Nawet w Haskell, gdzie programiści bardzo poważnie podchodzą do praw i pojęć matematycznych, nadal można przeciążać oba
==
i/=
, jak widać tutaj ( http://hackage.haskell.org/package/base-4.9.0.0/docs/Prelude .html # v: -61--61- ):Prawdopodobnie byłoby to uważane za mikrooptymalizację, ale w niektórych przypadkach może być uzasadnione.
źródło
pcmpeqb
instrukcja, ale nie ma instrukcji porównawczej, która tworzy maskę! =. Więc jeśli nie możesz po prostu odwrócić logiki tego, co korzysta z wyników, musisz użyć innej instrukcji, aby ją odwrócić. (Ciekawostka: zestaw instrukcji XOP AMD ma już wiele zestawieńneq
. Szkoda, że Intel nie przyjął / nie rozszerzył XOP; w przydatnym rozszerzeniu ISA znajduje się kilka przydatnych instrukcji.)PXOR
razem ze wszystkimi, aby odwrócić wynik porównania maski) w ciasnej pętli może mieć znaczenie.x == y
kosztują znacznie więcej niżx != y
. Obliczenie tego drugiego może być znacznie tańsze ze względu na przewidywanie gałęzi itp.To jest opinia. Może nie. Ale projektanci języka, nie będąc wszechwiedzący, postanowili nie ograniczać ludzi, którzy mogą wymyślić sytuacje, w których może to mieć sens (przynajmniej dla nich).
źródło
W odpowiedzi na edycję;
W ogóle , nie, to nie ma sensu. Operatory równości i relacji są zazwyczaj dostępne w zestawach. Jeśli jest równość, to również nierówność; mniej niż, to więcej niż i tak dalej z
<=
itd. Podobne podejście stosuje się również do operatorów arytmetycznych, one również ogólnie występują w naturalnych zestawach logicznych.Dowodzi tego fakt
std::rel_ops
przestrzeń nazw. Jeśli zaimplementujesz równość i mniej niż operatorów, użycie tej przestrzeni nazw daje ci inne, zaimplementowane w kategoriach oryginalnych zaimplementowanych operatorów.To powiedziawszy, czy istnieją warunki lub sytuacje, w których jedno nie oznaczałoby od razu drugiego lub nie można go wdrożyć w odniesieniu do innych? Tak, jest ich zapewne niewiele, ale oni tam są; ponownie, o czym świadczy
rel_ops
istnienie własnej przestrzeni nazw. Z tego powodu umożliwienie ich niezależnej implementacji umożliwia wykorzystanie języka w celu uzyskania potrzebnej lub potrzebnej semantyki w sposób, który jest nadal naturalny i intuicyjny dla użytkownika lub klienta kodu.Wspomniana już leniwa ocena jest tego doskonałym przykładem. Innym dobrym przykładem jest nadanie im semantyki, która wcale nie oznacza równości lub nierówności. Podobnym przykładem są operatory przesunięcia bitów
<<
i>>
stosowane do wstawiania i wydobywania strumienia. Chociaż może się to spotkać z niechęcią w kręgach ogólnych, w niektórych obszarach specyficznych dla domeny może mieć sens.źródło
Jeśli
==
i!=
operatorzy nie faktycznie oznacza, równość, w taki sam sposób, że<<
i>>
operatorzy stream nie oznaczają przesunięcie bitów. Jeśli traktujesz te symbole tak, jakby oznaczały jakąś inną koncepcję, nie muszą się wzajemnie wykluczać.Jeśli chodzi o równość, może mieć sens, jeśli twoja przypadek użycia uzasadnia traktowanie obiektów jako nieporównywalnych, aby każde porównanie zwróciło fałsz (lub nieporównywalny typ wyniku, jeśli operatorzy zwrócą wartość inną niż bool). Nie mogę wymyślić konkretnej sytuacji, w której byłoby to uzasadnione, ale widziałem, że jest to wystarczająco rozsądne.
źródło
Z wielką mocą przychodzi świetnie odpowiedzialny, a przynajmniej naprawdę dobry styl przewodników.
==
i!=
może być przeciążony, aby zrobić cokolwiek chcesz. To zarówno błogosławieństwo, jak i przekleństwo. Nie ma gwarancji, że!=
to oznacza!(a==b)
.źródło
Nie mogę uzasadnić przeciążenia tego operatora, ale w powyższym przykładzie nie można zdefiniować
operator!=
jako „przeciwieństwo”operator==
.źródło
!=
tak naprawdę nie oznaczałoby to odwrotności==
.==
?W końcu sprawdzasz za pomocą tych operatorów, czy wyrażenie
a == b
luba != b
zwraca wartość logiczną (true
lubfalse
). Wyrażenie to zwraca wartość logiczną po porównaniu, a nie wyklucza się wzajemnie.źródło
Jedną rzeczą do rozważenia jest to, że może istnieć możliwość zaimplementowania jednego z tych operatorów bardziej efektywnie niż tylko zaprzeczenie drugiemu.
(Mój przykład tutaj to śmieci, ale kwestia nadal jest ważna, pomyśl o filtrach Bloom, na przykład: Pozwalają one na szybkie testowanie, jeśli czegoś nie ma w zestawie, ale sprawdzenie, czy jest w zestawie, może zająć dużo więcej czasu.)
I to jako programista ponosisz odpowiedzialność. Prawdopodobnie dobrze jest napisać test.
źródło
!((a == rhs.a) && (b == rhs.b))
nie dopuścić do zwarcia? jeśli!(a == rhs.a)
, to(b == rhs.b)
nie będzie oceniane.==
przestanie porównywać, gdy tylko pierwsze odpowiednie elementy nie będą równe. Ale w przypadku!=
, gdyby został zaimplementowany w kategoriach==
, musiałby najpierw porównać wszystkie odpowiadające mu elementy (gdy wszystkie są równe), aby móc stwierdzić, że nie są one różne: P Ale po zaimplementowaniu jak w powyższy przykład przestanie porównywać, gdy tylko znajdzie pierwszą nierównomierną parę. Rzeczywiście świetny przykład.!((a == b) && (c == d))
i(a != b) || (c != d)
są równoważne pod względem wydajności zwarciowej.Dostosowując zachowanie operatorów, możesz zmusić ich do robienia tego, co chcesz.
Możesz dostosować rzeczy. Na przykład możesz dostosować klasę. Obiekty tej klasy można porównać, po prostu sprawdzając określoną właściwość. Wiedząc, że tak jest, możesz napisać określony kod, który sprawdza tylko minimalne rzeczy, zamiast sprawdzać każdy bit każdej pojedynczej właściwości w całym obiekcie.
Wyobraź sobie przypadek, w którym możesz dowiedzieć się, że coś jest inne tak samo szybko, jeśli nie szybciej, niż możesz dowiedzieć się, że coś jest takie samo. To prawda, że kiedy zorientujesz się, czy coś jest takie samo czy różne, możesz poznać coś przeciwnego, po prostu trochę odwracając. Jednak odwrócenie tego bitu jest dodatkową operacją. W niektórych przypadkach, gdy kod jest często ponownie uruchamiany, zapisanie jednej operacji (pomnożonej przez wiele razy) może spowodować ogólny wzrost prędkości. (Na przykład, jeśli zapisujesz jedną operację na piksel ekranu megapikselowego, to właśnie zapisałeś milion operacji. Pomnożony przez 60 ekranów na sekundę, a ty oszczędzasz jeszcze więcej operacji.)
Odpowiedź hvd zawiera dodatkowe przykłady.
źródło
Tak, ponieważ jeden oznacza „ekwiwalent”, a drugi oznacza „ekwiwalent”, a te warunki wzajemnie się wykluczają. Wszelkie inne znaczenie dla tego operatora jest mylące i należy go unikać wszelkimi środkami.
źródło
a != b
nie jest równy z!(a == b)
tego powodu w C?Może nieporównywalne reguła, gdzie
a != b
był fałszywy ia == b
był fałszywy jak trochę bezpaństwowcem.źródło
operator==()
ioperator!=()
niekonieczniebool
, mogą być wyliczeniem, które obejmuje bezpaństwowców, jeśli chcesz, a mimo to operatory mogą być nadal zdefiniowane, więc(a != b) == !(a==b)
zachowuje się.