Właśnie przeczytałem jeden z artykułów Joela, w którym mówi:
Ogólnie rzecz biorąc, muszę przyznać, że trochę boję się funkcji językowych, które ukrywają rzeczy . Gdy zobaczysz kod
i = j * 5;
… W C wiesz przynajmniej, że j mnoży się przez pięć, a wyniki są przechowywane w i.
Ale jeśli widzisz ten sam fragment kodu w C ++, nic nie wiesz. Nic. Jedynym sposobem, aby dowiedzieć się, co tak naprawdę dzieje się w C ++, jest sprawdzenie, jakie są typy i i j, co można zadeklarować gdzie indziej. To dlatego, że j może być tego typu, który został
operator*
przeciążony i robi coś strasznie dowcipnego, gdy próbujesz go pomnożyć.
(Podkreśl moje.) Boisz się cech językowych, które ukrywają rzeczy? Jak możesz się tego bać? Czy ukrywanie rzeczy (zwanych również abstrakcją ) nie jest jedną z kluczowych idei programowania obiektowego? Za każdym razem, gdy wywołujesz metodę a.foo(b)
, nie masz pojęcia, co to może zrobić. Musisz dowiedzieć się, jakie są a
i jakie b
są typy , które mogą być zadeklarowane gdzie indziej. Czy powinniśmy więc zrezygnować z programowania obiektowego, ponieważ zbyt wiele rzeczy ukrywa przed programistą?
A czym się j * 5
różni od tego j.multiply(5)
, co możesz napisać w języku, który nie obsługuje przeciążania operatora? Ponownie musielibyście dowiedzieć się, jaki jest rodzaj metody j
i zajrzyj do niej multiply
, ponieważ oto j
może być multiply
metoda, która robi coś strasznie dowcipnego.
„Muahaha, jestem złym programistą, który nazywa metodę multiply
, ale to, co faktycznie robi, jest całkowicie niejasne i nieintuicyjne i nie ma absolutnie nic wspólnego z mnożeniem rzeczy”. Czy to scenariusz, który musimy wziąć pod uwagę przy projektowaniu języka programowania? Następnie musimy porzucić identyfikatory z języków programowania, ponieważ mogą wprowadzać w błąd!
Jeśli chcesz wiedzieć, co robi metoda, możesz rzucić okiem na dokumentację lub zajrzeć do wnętrza implementacji. Przeciążenie operatora to tylko cukier syntaktyczny i wcale nie widzę, jak zmienia to grę.
Proszę, oświeć mnie.
Odpowiedzi:
Abstrakcja „ukrywa” kod, abyś nie musiał martwić się wewnętrznymi działaniami i często nie mógłbyś ich zmienić, ale nie chciałeś, żebyś na to nie patrzył. Po prostu przyjmujemy założenia dotyczące operatorów i, jak powiedział Joel, może być wszędzie. Posiadanie funkcji programowania wymagającej ustanowienia wszystkich przeciążonych operatorów w określonej lokalizacji może pomóc w jej znalezieniu, ale nie jestem pewien, czy ułatwi to korzystanie z niej.
Nie widzę zmuszania * do robienia czegoś, co nie przypominałoby mnożenia lepiej niż funkcji o nazwie Get_Some_Data, która usuwa dane.
źródło
<<
operatora zdefiniowanego w strumieniach, który nie ma nic wspólnego z przesunięciem bitowym, bezpośrednio w standardowej bibliotece C ++.IMHO, funkcje językowe, takie jak przeciążenie operatora, dają programistowi więcej mocy. Jak wszyscy wiemy, z wielką mocą wiąże się wielka odpowiedzialność. Funkcje, które dają ci więcej mocy, dają także więcej sposobów na strzelanie sobie w stopę i, oczywiście, powinny być używane rozsądnie.
Na przykład, ma to sens, aby przeładowywać
+
lub*
operatora naclass Matrix
lubclass Complex
. Wszyscy natychmiast dowiedzą się, co to znaczy. Z drugiej strony, dla mnie fakt, że+
oznacza to łączenie łańcuchów, wcale nie jest oczywisty, mimo że Java robi to jako część języka, a STLstd::string
używa przeciążania operatora.Innym dobrym przykładem, gdy przeciążenie operatora czyni kod bardziej wyraźnym, są inteligentne wskaźniki w C ++. Chcesz, aby inteligentne wskaźniki zachowywały się jak zwykłe wskaźniki tak bardzo, jak to możliwe, więc sensowne jest przeciążanie jednostek jednoargumentowych
*
i->
operatorów.Zasadniczo przeciążanie operatora jest niczym innym, jak tylko innym sposobem nazwania funkcji. I jest zasada dla nazewnictwa funkcji: nazwa musi być opisowa, dzięki czemu natychmiast stanie się oczywiste, co robi funkcja. Ta sama dokładna zasada dotyczy przeciążenia operatora.
źródło
*
tych może powodować zamieszanie. Prawdopodobnie możesz użyćoperator*()
dla produktu kropkowego i produktuoperator%()
krzyżowego, ale nie zrobiłbym tego dla biblioteki ogólnego użytku.A-B
ponieważB-A
wszyscy operatorzy stosują ten schemat. Chociaż zawsze istnieje jeden wyjątek: gdy kompilator może udowodnić, że to nie ma znaczenia, wszystko można zmienić.W języku Haskell „+”, „-”, „*”, „/” itd. Są tylko funkcjami (infiksowymi).
Czy powinieneś nazwać funkcję infix „plus” jak w „4 plus 2”? Dlaczego nie, jeśli dodawanie jest funkcją twojej funkcji? Czy powinieneś nazwać swoją funkcję „plus” „+”? Dlaczego nie.
Myślę, że problem z tak zwanymi „operatorami” polega na tym, że w większości przypominają one operacje matematyczne i nie ma wielu sposobów ich interpretacji, a zatem istnieją wysokie oczekiwania co do tego, co robi taka metoda / funkcja / operator.
EDYCJA: wyjaśniłem mój punkt widzenia
źródło
int
,float
,long long
i cokolwiek. O co w tym wszystkim chodzi?+
dla różnych wbudowanych typów liczb, ale tworzeniu przeciążeń zdefiniowanych przez użytkownika. stąd mój komentarz.Na podstawie innych odpowiedzi, które widziałem, mogę jedynie stwierdzić, że prawdziwym sprzeciwem wobec przeciążenia operatora jest chęć natychmiastowego oczywistego kodu.
Jest to tragiczne z dwóch powodów:
źródło
Nieco się zgadzam.
Jeśli piszesz
multiply(j,5)
,j
może być typu skalarnego lub macierzowego, co czynimultiply()
mniej lub bardziej złożoną, w zależności od tego, coj
jest. Jeśli jednak całkowicie porzucisz pomysł przeciążenia, funkcja będzie musiała zostać nazwanamultiply_scalar()
lubmultiply_matrix()
zrozumiałe, co dzieje się pod spodem.Jest kod, w którym wielu z nas wolałoby go w jeden sposób, i jest kod, w którym większość z nas wolałaby w inny sposób. Większość kodu wpada jednak w środek ziemi między tymi dwoma skrajnościami. To, co wolisz, zależy od twojego pochodzenia i osobistych preferencji.
źródło
multiply_matrix()
, nie lubią programowania ogólnego.real_multiply()
mniej więcej. Deweloperzy często nie są dobrzy w nazwach, aoperator*()
przynajmniej będą konsekwentni.operator*()
może zrobić coś głupiego,j
to makro oceniające wyrażenia obejmujące pięć wywołań funkcji i tak dalej. Wówczas nie można już porównywać dwóch podejść. Ale tak, dobre nazywanie rzeczy jest trudne, choć warte czasu.Widzę dwa problemy z przeciążeniem operatora.
&&
,||
albo,
tracisz punkty sekwencji, które są regulowane przez wbudowany w wariantach tych operatorów (jak również zachowanie zwarcie operatorów logicznych). Z tego powodu lepiej nie przeciążać tych operatorów, nawet jeśli pozwala na to język.operator<<
strumienie.źródło
&&
iw||
sposób, który nie sugeruje sekwencjonowania, było dużym błędem (IMHO, jeśli C ++ miałby pozwolić na przeładowanie tych, powinien był użyć specjalnego formatu „dwufunkcyjnego”, przy czym wymagana była pierwsza funkcja aby zwrócić typ, który domyślnie można było przekształcić na liczbę całkowitą; druga funkcja może przyjąć dwa lub trzy argumenty, przy czym „dodatkowy” argument drugiej funkcji jest typem zwracanym przez pierwszą. Kompilator wywoła pierwszą funkcję, a następnie, jeśli zwrócił wartość niezerową, oceń drugi operand i wywołaj na nim drugą funkcję.)foo.bar[3].X
dofoo
klasy, zamiast wymagaćfoo
ujawnienia elementu, który może obsługiwać indeksowanie, a następnie ujawnić członkaX
. Gdyby ktoś chciał wymusić ocenę poprzez faktyczny dostęp do członka, napisałby((foo.bar)[3]).X
.W oparciu o moje osobiste doświadczenia, Java zezwalający na wiele metod, ale bez przeciążania operatora, oznacza, że ilekroć widzisz operatora, wiesz dokładnie, co on robi.
Nie musisz sprawdzać, czy
*
wywołuje dziwny kod, ale wiesz, że jest to wielokrotność i zachowuje się dokładnie tak, jak zdefiniowano w specyfikacji języka Java. Oznacza to, że możesz skoncentrować się na faktycznym zachowaniu, zamiast znajdować wszystkie furtki zdefiniowane przez programistę.Innymi słowy, zakaz przeciążania operatora jest korzyścią dla czytelnika , a nie dla pisarza , a zatem ułatwia utrzymanie programów!
źródło
list.get(n)
składnią?std::list
nie przeciążająoperator[]
(ani nie dają żadnych innych metod indeksowania do listy), ponieważ taka operacja to O (n), a interfejs listy nie powinien ujawniać takiej funkcji, jeśli zależy Ci na wydajności. Klienci mogą ulec pokusie iteracji po połączonych listach z indeksami, przez co algorytmy O (n) niepotrzebnie O (n ^ 2). Widzisz to dość często w kodzie Java, szczególnie jeśli ludzie pracują zList
interfejsem, którego celem jest całkowite wyeliminowanie złożoności.time.add(anotherTime)
, musisz także sprawdzić, czy programista biblioteki „poprawnie” zaimplementował operację dodawania (cokolwiek to oznacza).Jedną z różnic między przeciążeniem
a * b
a wywoływaniemmultiply(a,b)
jest to, że można łatwo go złapać. Jeślimultiply
funkcja nie jest przeciążona dla różnych typów, możesz dowiedzieć się dokładnie, co ta funkcja zrobi, bez konieczności śledzenia typówa
ib
.Linus Torvalds ma interesujący argument na temat przeciążenia operatora. W czymś takim jak tworzenie jądra linuksa, w którym większość zmian jest wysyłana za pośrednictwem łatek przez e-mail, ważne jest, aby opiekunowie zrozumieli, co zrobi łatka, mając tylko kilka linii kontekstu wokół każdej zmiany. Jeśli funkcje i operatory nie są przeciążone, łatkę można łatwiej odczytać w sposób niezależny od kontekstu, ponieważ nie trzeba przechodzić przez zmieniony plik, sprawdzając, jakie są wszystkie typy i sprawdzać przeciążonych operatorów.
źródło
Podejrzewam, że ma to coś wspólnego z przełamywaniem oczekiwań. Przyzwyczaiłem się do C ++, przyzwyczaiłeś się do tego, że zachowanie operatora nie jest całkowicie podyktowane przez język i nie będziesz zaskoczony, gdy operator zrobi coś dziwnego. Jeśli jesteś przyzwyczajony do języków, które nie mają tej funkcji, a następnie widzisz kod C ++, przyniesiesz oczekiwania względem tych innych języków i możesz być zaskoczony, gdy odkryjesz, że przeciążony operator robi coś dziwnego.
Osobiście uważam, że jest różnica. Kiedy możesz zmienić zachowanie wbudowanej składni języka, staje się bardziej niejasny. Języki, które nie pozwalają na metaprogramowanie, są mniej wydajne pod względem składniowym, ale są łatwiejsze do zrozumienia pod względem koncepcyjnym.
źródło
Myślę, że przeciążanie operatorów matematycznych nie jest prawdziwym problemem z przeciążaniem operatorów w C ++. Uważam, że przeciążanie operatorów, które nie powinny polegać na kontekście wyrażenia (tj. Typu), jest „złe”. Np. Przeciążenie,
,
[ ]
( )
->
->*
new
delete
a nawet jedność*
. Masz pewien zestaw oczekiwań od tych operatorów, którzy nigdy nie powinni się zmienić.źródło
operator[]
, funktorów bezoperator()
, inteligentnych wskaźników bezoperator->
i tak dalej.[]
powinno zawsze być akcesorium tablicowym i->
zawsze powinno oznaczać dostęp do członka. Nie ma znaczenia, czy faktycznie jest to tablica, czy inny kontener, czy jest to inteligentny wskaźnik, czy nie.Rozumiem, że nie podoba ci się argument Joela o ukrywaniu się. Ja też nie. O wiele lepiej jest używać „+” do takich rzeczy, jak wbudowane typy numeryczne lub do własnych, takich jak np. Macierz. Przyznaję, że fajnie i elegancko jest pomnożyć dwie macierze za pomocą „*” zamiast „.multiply ()”. A przecież w obu przypadkach mamy ten sam rodzaj abstrakcji.
Boli tu czytelność twojego kodu. W rzeczywistych przypadkach, nie w akademickim przykładzie mnożenia macierzy. Zwłaszcza jeśli twój język pozwala na przykład zdefiniować operatory, które początkowo nie są obecne w rdzeniu językowym
=:=
. W tym momencie pojawia się wiele dodatkowych pytań. O co chodzi z tym cholernym operatorem? Mam na myśli, jaki jest priorytet tej rzeczy? Co to jest skojarzenie? W jakiej kolejności jesta =:= b =:= c
naprawdę wykonywane?To już argument przeciwko przeciążeniu operatora. Nadal nie jesteś przekonany? Sprawdzenie reguł pierwszeństwa zajęło Ci nie więcej niż 10 sekund? Ok, chodźmy dalej.
Jeśli zaczniesz używać języka, który pozwala na przeciążanie operatora, na przykład tego popularnego, którego nazwa zaczyna się na „S”, szybko dowiesz się, że projektanci bibliotek uwielbiają zastępować operatorów. Oczywiście są dobrze wykształceni, postępują zgodnie z najlepszymi praktykami (tutaj nie ma cynizmu), a wszystkie ich interfejsy API mają idealny sens, gdy spojrzymy na nie oddzielnie.
Teraz wyobraź sobie, że musisz użyć kilku interfejsów API, które często wykorzystują operatorów przeciążających się razem w jednym kawałku kodu. Lub jeszcze lepiej - musisz przeczytać taki starszy kod. To wtedy przeciążenie operatora jest naprawdę do bani. Zasadniczo, jeśli w jednym miejscu jest dużo przeciążonych operatorów, wkrótce zaczną się one mieszać z innymi znakami niealfanumerycznymi w kodzie programu. Będą mieszać się ze znakami alfanumerycznymi, które nie są tak naprawdę operatorami, ale raczej bardziej podstawowymi elementami gramatyki języka, które definiują takie elementy, jak bloki i zakresy, instrukcje kontroli przepływu kształtu lub oznaczają niektóre meta-rzeczy. Musisz zrozumieć okulary i przesunąć oczy 10 cm bliżej wyświetlacza LCD, aby zrozumieć ten wizualny bałagan.
źródło
Zasadniczo unikam przeciążania operatora w nieintuicyjny sposób. To znaczy, jeśli mam klasę numeryczną, przeciążanie * jest dopuszczalne (i zalecane). Jeśli jednak mam klasowego pracownika, co spowodowałoby przeciążenie *? Innymi słowy, przeciążają operatorów w intuicyjny sposób, który ułatwia czytanie i zrozumienie.
Dopuszczalne / zalecane:
Nie do przyjęcia:
źródło
Oprócz tego, co już tu powiedzieliśmy, jest jeszcze jeden argument przeciwko przeciążeniu operatora. Rzeczywiście, jeśli piszesz
+
, jest to oczywiste, że masz na myśli dodanie czegoś do czegoś. Lecz nie zawsze tak jest.Sam C ++ stanowi doskonały przykład takiego przypadku. Jak
stream << 1
należy czytać? strumień przesunięty w lewo o 1? Nie jest to wcale oczywiste, chyba że wyraźnie wiesz, że << w C ++ również zapisuje w strumieniu. Gdyby jednak ta operacja została zaimplementowana jako metoda, żaden rozsądny programista nie napisałbyo.leftShift(1)
, byłoby to coś w rodzajuo.write(1)
.Najważniejsze jest to, że poprzez uniemożliwienie przeciążenia operatora język sprawia, że programiści myślą o nazwach operacji. Nawet jeśli wybrana nazwa nie jest idealna, trudniej jest błędnie zinterpretować nazwę niż znak.
źródło
W porównaniu do metod pisowni operatory są krótsze, ale także nie wymagają nawiasów. Nawiasy są stosunkowo niewygodne do pisania. I musisz je zrównoważyć. W sumie każde wywołanie metody wymaga trzech znaków zwykłego hałasu w porównaniu do operatora. To sprawia, że używanie operatorów jest bardzo kuszące.
Dlaczego ktoś miałby tego chcieć
cout << "Hello world"
:?Problem z przeciążeniem polega na tym, że większość programistów jest niewiarygodnie leniwa i na większość programistów nie stać.
To, co doprowadza programistów C ++ do nadużywania przeciążenia operatora, to nie jego obecność, ale brak ładniejszego sposobu wykonywania wywołań metod. Ludzie obawiają się nie tylko przeciążenia operatora, ponieważ jest to możliwe, ale dlatego, że jest zrobione.
Zauważ, że na przykład w Ruby i Scali nikt nie boi się przeciążenia operatora. Poza tym, że użycie operatorów nie jest tak naprawdę krótsze niż metody, innym powodem jest to, że Ruby ogranicza przeciążanie operatora do rozsądnego minimum, podczas gdy Scala pozwala zadeklarować własnych operatorów, dzięki czemu unikanie kolizji jest banalne.
źródło
Przeciążenie operatora jest przerażające, ponieważ istnieje duża liczba programistów, którzy nigdy nawet
*
NIE MYŚLĄ, co nie oznacza po prostu „pomnożenie”, podczas gdy metoda taka jakfoo.multiply(bar)
przynajmniej natychmiast wskazuje programistowi, że ktoś napisał niestandardową metodę mnożenia . W tym momencie zastanawiają się, dlaczego i idą na śledztwo.Pracowałem z „dobrymi programistami”, którzy zajmowali wysokie stanowiska, tworząc metody o nazwie „CompareValues”, które przyjmowałyby 2 argumenty, stosowały wartości od jednego do drugiego i zwracały wartość logiczną. Lub metoda o nazwie „LoadTheValues”, która trafiłaby do bazy danych dla 3 innych obiektów, uzyskała wartości, wykonała obliczenia, zmodyfikowała
this
i zapisała ją w bazie danych.Jeśli pracuję w zespole z programistami tego typu, od razu wiem, jak zbadać rzeczy, nad którymi pracowali. Jeśli przeciążyli operatora, nie mam pojęcia, że to zrobili, poza założeniem, że to zrobili i szukają.
W idealnym świecie lub zespole z doskonałymi programistami przeciążenie operatora jest prawdopodobnie fantastycznym narzędziem. Muszę jednak pracować nad zespołem doskonałych programistów, dlatego jest to przerażające.
źródło