Nie rozumiem argumentów przeciwko przeciążeniu operatora [zamknięte]


82

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ą ai jakie bsą 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 * 5róż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 ji zajrzyj do niej multiply, ponieważ oto jmoże być multiplymetoda, 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.


20
+1: Dobrze napisany, dobrze argumentowany, ciekawy temat i wysoce dyskusyjny. Świecący przykład pytania p.se.
Allon Guralnek,

19
+1: Ludzie słuchają Joela Spolsky'ego, ponieważ dobrze pisze i jest dobrze znany. Ale to nie czyni go dobrym w 100% przypadków. Zgadzam się z twoim argumentem. Gdybyśmy wszyscy przestrzegali logiki Joela tutaj, nigdy byśmy nie dotarli.
Nikt

5
Argumentowałbym, że albo i i są deklarowane lokalnie, aby można było szybko zobaczyć ich typ, lub że są to zmienne nazwy zmiennych i należy je odpowiednio zmienić.
Cameron MacFarland,

5
+1, ale nie zapominaj o najlepszej części artykułu Joela: po przejechaniu maratonu w kierunku poprawnej odpowiedzi, bez wyraźnego powodu zatrzymuje się 50 stóp przed nią. Zły kod nie powinien po prostu wyglądać źle; nie powinno się kompilować.
Larry Coleman,

3
@ Larry: Możesz sprawić, że niewłaściwy kod nie zostanie skompilowany poprzez odpowiednie zdefiniowanie klas, więc w jego przykładzie możesz mieć SafeString i UnsafeString w C ++ lub RowIndex i ColumnIndex, ale musiałbyś wtedy użyć przeciążenia operatora, aby zachowywały się intuicyjnie.
David Thornley,

Odpowiedzi:


32

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.


13
+1 Za bit „Nie widzę”. Funkcje językowe są dostępne, a nie nadużycia.
Michael K,

5
Mamy jednak <<operatora zdefiniowanego w strumieniach, który nie ma nic wspólnego z przesunięciem bitowym, bezpośrednio w standardowej bibliotece C ++.
Malcolm,

operator „przesunięcia bitowego” nazywa się tak ze względów historycznych. Po zastosowaniu do typów standardowych dokonuje przesunięcia bitowego (w ten sam sposób, w jaki operator + dodaje liczby razem, gdy jest stosowany do typów numerycznych), jednak po zastosowaniu do typu złożonego może robić to, co lubi, pod warunkiem, że powoduje sens dla tego typu.
gbjbaanb

1
* służy również do dereferencji (tak jak robią to inteligentne wskaźniki i iteratory); nie jest jasne, gdzie umieścić granicę między przeciążeniem dobrym a złym
martinkunev

Nie byłoby go nigdzie, byłoby w definicji typu j.
Andy,

19

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 na class Matrixlub class 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 STL std::stringuż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.


1
Ostatnie dwa zdania trafiają w sedno sprzeciwu wobec przeciążenia operatora: pragnienie, aby cały kod był natychmiast oczywisty.
Larry Coleman,

2
Czy nie jest oczywiste, co oznacza M * N, gdzie M i N są typu Matrix?
Dima,

2
@Fred: Nie. Istnieje jeden rodzaj mnożenia macierzy. Możesz pomnożyć macierz mxn przez macierz nxk i uzyskać macierz mxk.
Dima,

1
@FredOverflow: Istnieją różne sposoby pomnożenia trójwymiarowego wektora, jeden daje skalar, a drugi inny trójwymiarowy wektor, a więc przeciążenie *tych może powodować zamieszanie. Prawdopodobnie możesz użyć operator*()dla produktu kropkowego i produktu operator%()krzyżowego, ale nie zrobiłbym tego dla biblioteki ogólnego użytku.
David Thornley,

2
@Martin Beckett: Nie. C ++ nie może również zmieniać kolejności, A-Bponieważ B-Awszyscy operatorzy stosują ten schemat. Chociaż zawsze istnieje jeden wyjątek: gdy kompilator może udowodnić, że to nie ma znaczenia, wszystko można zmienić.
Sjoerd,

9

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


Erm, z wyjątkiem tego, co odziedziczyłem po C, C ++ (i o to właśnie pytał Fred) robi prawie to samo. Jakie jest twoje zdanie na temat tego, czy jest to dobre czy złe?
sbi,

@sbi Kocham przeciążanie operatorów ... Właściwie nawet C ma przeciążone operatory ... Można ich używać do int, float, long longi cokolwiek. O co w tym wszystkim chodzi?
FUZxxl,

@FUZxxl: Chodzi o operatory zdefiniowane przez użytkownika przeciążające wbudowane.
sbi

1
@sbi Haskell nie ma rozróżnienia między wbudowanym a zdefiniowanym przez użytkownika . Wszyscy operatorzy są równi. Możesz nawet włączyć niektóre rozszerzenia, które usuwają wszystkie predefiniowane elementy i pozwalają pisać od zera, w tym dowolne operatory.
FUZxxl,

@FUZxxl: Może tak być, ale ci przeciwnicy przeciążonych operatorów zwykle nie sprzeciwiają się użyciu wbudowanego +dla różnych wbudowanych typów liczb, ale tworzeniu przeciążeń zdefiniowanych przez użytkownika. stąd mój komentarz.
sbi

7

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:

  1. Zgodnie z logicznym wnioskiem zasada, że ​​kod powinien być natychmiast oczywisty, sprawiłby, że wszyscy nadal kodujemy w języku COBOL.
  2. Nie uczysz się na podstawie kodu, który jest natychmiast oczywisty. Uczysz się z kodu, który ma sens, gdy poświęcisz trochę czasu na zastanowienie się, jak to działa.

Uczenie się na podstawie kodu nie zawsze jest jednak głównym celem. W przypadku, gdy „funkcja X jest zepsuta, osoba, która ją napisała, opuściła firmę i trzeba ją jak najszybciej naprawić”, wolałbym mieć kod, który jest natychmiast oczywisty.
Errorsatz

5

Nieco się zgadzam.

Jeśli piszesz multiply(j,5), jmoże być typu skalarnego lub macierzowego, co czyni multiply()mniej lub bardziej złożoną, w zależności od tego, co jjest. Jeśli jednak całkowicie porzucisz pomysł przeciążenia, funkcja będzie musiała zostać nazwana multiply_scalar()lub multiply_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.


Słuszna uwaga. Jednak całkowite porzucenie przeciążenia nie działa dobrze z programowaniem ogólnym ...
fredoverflow

@FredO: Oczywiście, że nie. Ale programowanie ogólne polega na użyciu tego samego algorytmu dla bardzo różnych typów, więc ci, którzy preferują multiply_matrix(), nie lubią programowania ogólnego.
sbi,

2
Jesteś raczej optymistą co do nazwisk, prawda? W oparciu o niektóre miejsca, w których pracowałem, spodziewałbym się nazw takich jak „mnożenie ()” i „mnożenie ()”, a może real_multiply()mniej więcej. Deweloperzy często nie są dobrzy w nazwach, a operator*()przynajmniej będą konsekwentni.
David Thornley,

@David: Tak, pominąłem fakt, że nazwy mogą być złe. Ale równie dobrze możemy założyć, że operator*()może zrobić coś głupiego, jto 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.
sbi,

5
@David: A ponieważ nazywanie rzeczy jest trudne, należy usunąć nazwy z języków programowania, prawda? Zbyt łatwo jest ich pomylić! ;-)
fredoverflow

4

Widzę dwa problemy z przeciążeniem operatora.

  1. Przeładowanie zmienia semantykę operatora, nawet jeśli nie jest to zamierzone przez programistę. Na przykład, gdy przeciążenie &&, ||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.
  2. Niektórzy uważają, że przeciążenie operatora jest tak fajną funkcją, że zaczynają go używać wszędzie, nawet jeśli nie jest to właściwe rozwiązanie. Powoduje to, że inni reagują nadmiernie w przeciwnym kierunku i ostrzegają przed przeciążeniem operatora. Nie zgadzam się z żadną z tych grup, ale stawiam na pierwszym miejscu: przeciążanie operatora powinno być stosowane oszczędnie i tylko wtedy
    • przeciążony operator ma naturalne znaczenie zarówno dla ekspertów domeny, jak i ekspertów oprogramowania. Jeśli te dwie grupy nie zgadzają się co do naturalnego znaczenia dla operatora, nie przeciążaj go.
    • dla typu (ów) nie ma naturalnego znaczenia dla operatora, a bezpośredni kontekst (najlepiej to samo wyrażenie, ale nie więcej niż kilka wierszy) zawsze wyjaśnia, jakie znaczenie ma operator. Przykładem tej kategorii mogą być operator<<strumienie.

1
+1 ode mnie, ale drugi argument można równie dobrze zastosować do dziedziczenia. Wiele osób nie ma pojęcia o dziedziczeniu i próbuje zastosować to do wszystkiego. Myślę, że większość programistów zgodziłaby się, że możliwe jest niewłaściwe użycie dziedziczenia. Czy to oznacza, że ​​dziedziczenie jest „złe” i należy je porzucić z języków programowania? A może powinniśmy to zostawić, ponieważ może być również przydatne?
fredoverflow

@FredOverflow Drugi argument można zastosować do wszystkiego, co jest „nowe i gorące”. Nie podam tego jako argumentu na rzecz usunięcia przeciążenia operatora z języka, ale jako powód, dla którego ludzie się temu sprzeciwiają. Moim zdaniem przeciążenie operatora jest przydatne, ale powinno się z nim obchodzić ostrożnie.
Bart van Ingen Schenau,

IMHO, dopuszczanie przeciążeń &&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ę.)
supercat

Oczywiście nie jest to tak dziwaczne, jak przeciążenie operatora przecinka. Nawiasem mówiąc, jedna przeciążająca rzecz, której tak naprawdę nie widziałem, ale chciałbym, byłaby sposobem na ścisły dostęp do elementu, pozwalając na wyrażenie podobne foo.bar[3].Xdo fooklasy, zamiast wymagać fooujawnienia elementu, który może obsługiwać indeksowanie, a następnie ujawnić członka X. Gdyby ktoś chciał wymusić ocenę poprzez faktyczny dostęp do członka, napisałby ((foo.bar)[3]).X.
supercat

3

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!


+1, z zastrzeżeniem: C ++ daje wystarczająco dużo liny, aby się powiesić. Ale jeśli chcę zaimplementować listę połączoną w C ++, chciałbym mieć możliwość użycia [] w celu uzyskania dostępu do n-tego elementu. Sensowne jest używanie operatorów do danych, dla których (matematycznie) są one ważne.
Michael K,

@Michael, nie możesz żyć ze list.get(n)składnią?

@ Thorbjørn: Właściwie to też dobrze, być może kiepski przykład. Czas może być lepszy - przeciążenie +, - miałoby sens raczej niż time.add (anotherTime).
Michael K,

4
@Michael: Informacje o połączonych listach std::listnie 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ą z Listinterfejsem, którego celem jest całkowite wyeliminowanie złożoności.
fredoverflow

5
@Thor: „Ale aby się upewnić, musisz sprawdzić :)”… Ponownie nie jest to związane z przeciążeniem operatora . Jeśli zobaczysz time.add(anotherTime), musisz także sprawdzić, czy programista biblioteki „poprawnie” zaimplementował operację dodawania (cokolwiek to oznacza).
fredoverflow

3

Jedną z różnic między przeciążeniem a * ba wywoływaniem multiply(a,b)jest to, że można łatwo go złapać. Jeśli multiplyfunkcja nie jest przeciążona dla różnych typów, możesz dowiedzieć się dokładnie, co ta funkcja zrobi, bez konieczności śledzenia typów ai b.

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.


Czy jądro Linuksa nie jest rozwijane w czystym C? Po co w ogóle omawiać przeciążanie (operatora) w tym kontekście?
fredoverflow

Obawy są takie same dla każdego projektu o podobnym procesie rozwoju, niezależnie od języka. Nadmierne przeciążenie może utrudnić zrozumienie wpływu zmian, jeśli wystarczy przejść kilka wierszy z pliku poprawki.
Scott Wales,

@FredOverflow: Jądro Linux jest naprawdę w GCC C. Korzysta z różnego rodzaju rozszerzeń, które czasem nadają jej C prawie C ++. Myślę o niektórych fantazyjnych manipulacjach.
Zan Lynx,

2
@ Scott: Nie ma sensu dyskutować o „szataństwie” przeciążenia w stosunku do projektów zaprogramowanych w C, ponieważ C nie ma możliwości przeciążenia funkcji.
fredoverflow

3
Linus Torvalds wydaje mi się mieć wąski punkt widzenia. Czasami krytykuje rzeczy, które tak naprawdę nie są przydatne w programowaniu jądra Linuksa, ponieważ sprawia, że ​​nie nadają się do ogólnego użytku. Subversion jest jednym z przykładów. To przyjemny VCS, ale rozwój jądra Linuksa naprawdę potrzebuje rozproszonego VCS, więc Linus ogólnie skrytykował SVN.
David Thornley,

2

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.


Przeciążeni operatorzy nigdy nie powinni robić „czegoś dziwnego”. Oczywiście jest w porządku, jeśli robi coś złożonego. Ale przeciąża się tylko wtedy, gdy ma jedno, oczywiste znaczenie.
Sjoerd,

2

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 deletea nawet jedność *. Masz pewien zestaw oczekiwań od tych operatorów, którzy nigdy nie powinni się zmienić.


+1 Nie rób [] odpowiednikiem ++.
Michael K,

3
Czy mówisz, że nie powinniśmy być w stanie przeciążać operatorów, o których wspomniałeś w ogóle ? A może po prostu mówisz, że powinniśmy je przeciążać tylko dla zdrowych celów? Ponieważ nie chciałbym widzieć pojemników bez operator[], funktorów bez operator(), inteligentnych wskaźników bez operator->i tak dalej.
fredoverflow

Mówię, że potencjalny problem przeciążenia operatora przez operacje matematyczne jest niewielki w porównaniu do tych operatorów. Robienie czegoś sprytnego lub szalonego z operatorami matematyki może być kłopotliwe, ale wymienione przeze mnie operatory, których ludzie zwykle nie uważają za operatorów, a raczej podstawowe elementy językowe, zawsze powinny spełniać oczekiwania określone przez język. []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.
Allon Guralnek,

2

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 jest a =:= b =:= cnaprawdę 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.


1
Przeciążanie istniejących operatorów i wymyślanie nowych operatorów to nie to samo, ale +1 ode mnie.
fredoverflow

1

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:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

Nie do przyjęcia:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};

1
Mnożenie pracowników? Z pewnością jest to przestępstwo, które można popełnić, jeśli zrobią to na stole w sali konferencyjnej.
gbjbaanb

1

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 << 1należ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łby o.leftShift(1), byłoby to coś w rodzaju o.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.


1

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.


lub, w języku C #, za pomocą + =, aby powiązać wydarzenie z delegatem. Nie sądzę, aby obwinianie języka za głupotę programistów było konstruktywnym krokiem naprzód.
gbjbaanb

0

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 jak foo.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 thisi 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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.