Co sprawia, że ​​operator Scali jest „dobry”, a C ++ „zły”?


155

Przeciążanie operatorów w C ++ jest przez wielu uważane za złą rzecz (tm) i błąd, który nie powinien być powtarzany w nowszych językach. Z pewnością była to jedna z funkcji, która została specjalnie usunięta podczas projektowania języka Java.

Teraz, gdy zacząłem czytać w Scali, stwierdziłem, że wygląda to bardzo podobnie do przeciążenia operatorów (chociaż technicznie nie ma przeciążenia operatorów, ponieważ nie ma operatorów, tylko funkcje). Jednak jakościowo nie wyglądałoby to inaczej niż przeciążenie operatora w C ++, gdzie, jak pamiętam, operatory są definiowane jako funkcje specjalne.

Więc moje pytanie brzmi: co sprawia, że ​​pomysł zdefiniowania „+” w Scali jest lepszym pomysłem niż w C ++?


27
Ani C ++, ani Scala nie zostały zdefiniowane w drodze powszechnej zgody wszystkich programistów. Nie sądzę, żeby istniała żadna sprzeczność między faktem, że niektórzy ludzie narzekają na C ++, a faktem, że niektórzy nie narzekają na Scala.
Steve Jessop

16
Nie ma nic złego w przeciążaniu operatorów w C ++.
Szczeniak

5
Nie jest to nic nowego, ale sposób, w jaki bronię C ++, gdy przeciążenie operatora i inne „zaawansowane” funkcje zostaną zakwestionowane, jest prosty: C ++ daje nam całą moc, aby używać / nadużywać go według własnego uznania. Zawsze podobało mi się, że jesteśmy kompetentni i niezależni i nie potrzebujemy takich decyzji podejmowanych za nas.
Elliott

Scala została zaprojektowana jak dekady po C ++. Okazuje się, że osoba stojąca za tym jest super-mądra w zakresie języków programowania. Nie ma w tym nic złego, jeśli będziesz trzymać się c ++ lub Scali przez kolejne 100 lat, stanie się jasne, że prawdopodobnie oba są złe! Bycie stronniczym jest najwyraźniej w naszej naturze, ale możemy z tym walczyć, wystarczy spojrzeć na historię technologii, wszystko staje się przestarzałe.
Nader Ghanbari

Odpowiedzi:


242

C ++ dziedziczy prawdziwe niebieskie operatory z C. Rozumiem przez to, że „+” w 6 + 4 jest bardzo szczególne. Nie możesz na przykład pobrać wskaźnika do tej + funkcji.

Z drugiej strony Scala nie ma operatorów w ten sposób. Ma po prostu dużą elastyczność w definiowaniu nazw metod oraz trochę wbudowanego pierwszeństwa dla symboli niebędących słowami. Więc technicznie Scala nie ma przeciążenia operatora.

Jakkolwiek chcesz to nazwać, przeciążenie operatorów nie jest z natury złe, nawet w C ++. Problem polega na tym, że nadużywają go źli programiści. Ale szczerze mówiąc, jestem zdania, że ​​odebranie programistom możliwości nadużywania przeciążenia operatorów nie jest kroplą w morzu w naprawianiu wszystkich rzeczy, które programiści mogą nadużywać. Prawdziwą odpowiedzią jest mentoring. http://james-iry.blogspot.com/2009/03/operator-overloading-ad-absurdum.html

Niemniej jednak istnieją różnice między przeciążeniem operatorów C ++ a elastycznym nazewnictwem metod Scali, które, IMHO, sprawiają, że Scala jest zarówno mniej nadużywalna, jak i bardziej nadużywalna.

W C ++ jedynym sposobem uzyskania notacji w poprawkach jest użycie operatorów. W przeciwnym razie musisz użyć obiektu.message (argument) lub pointer-> messsage (argument) lub funkcji (argument1, argument2). Więc jeśli chcesz mieć określony styl DSLish w swoim kodzie, istnieje presja, aby używać operatorów.

W Scali możesz otrzymać notację wrostkową z każdą wysłaną wiadomością. "argument obiektu wiadomości" jest całkowicie w porządku, co oznacza, że ​​nie musisz używać symboli innych niż słowo, aby uzyskać notację wrostkową.

Przeciążanie operatorów C ++ jest ograniczone zasadniczo do operatorów C. W połączeniu z ograniczeniem polegającym na tym, że tylko operatory mogą być używane wrostek, który wywiera presję na ludziach, aby próbowali odwzorować szeroki zakres niepowiązanych pojęć na stosunkowo nieliczne symbole, takie jak „+” i „>>”

Scala dopuszcza szeroką gamę ważnych symboli niebędących słowami jako nazw metod. Na przykład mam wbudowane DSL w Prologu, w którym możesz pisać

female('jane)!         // jane is female
parent('jane,'john)!   // jane is john's parent
parent('jane, 'wendy)! // jane is wendy's parent

mother('Mother, 'Child) :- parent('Mother, 'Child) & female('Mother) //'// a mother of a child is the child's parent and is female

mother('X, 'john)?  // find john's mother
mother('jane, 'X)?  // find's all of jane's children

Symbole: -,!,? I & są zdefiniowane jako zwykłe metody. Tylko w C ++ & byłby ważny, więc próba odwzorowania tego DSL na C ++ wymagałaby pewnych symboli, które już wywołują bardzo różne koncepcje.

Oczywiście otwiera to również Scalę na inny rodzaj nadużyć. W Scali możesz nazwać metodę $! & ^%, Jeśli chcesz.

W przypadku innych języków, które, tak jak Scala, są elastyczne w używaniu funkcji niebędących słowami i nazw metod, zobacz Smalltalk, gdzie, podobnie jak Scala, każdy "operator" jest tylko inną metodą i Haskell, który pozwala programiście definiować pierwszeństwo i stałość elastycznie nazwanych Funkcje.


Ostatnio sprawdzałem, 3. operator + (5) zadziałało. Jestem naprawdę zaskoczony, że & (3. operator +) nie.
Joshua,

możesz na przykład wykonać assert (female ("jane")) w języku c ++. To wcale nie byłoby mylące - kiwnę głową w stronę posta Jamesa iry'ego, w którym nie jest to fakt, że operator + jest złą rzeczą, ale głupi programiści tak.
pm100

1
int main() {return (3).operator+(5);}Wyniki @Joshuaerror: request for member ‘operator+’ in ‘3’, which is of non-class type ‘int’
zildjohn01

To kupa aroganckich bzdur: „Przeciążanie operatorów nie jest z natury złe, nawet w C ++. Problem polega na tym, że źli programiści nadużywają go”. Jeśli coś jest łatwe do nadużycia, przy niewielkich korzyściach z jego używania, ogólny rezultat jest taki, że następny facet zajmujący się twoim kodem straci wydajność w rozszyfrowywaniu dziwnych części twojego kodu. W przeciwnym razie: bardzo pouczająca i dobrze napisana odpowiedź.
Jukka Dahlbom

@JukkaDahlbom Samo istnienie inteligentnych wskaźników sprawia, że ​​korzyści są duże. A potem masz lambdy, typy liczb zdefiniowane przez użytkownika, typy interwałów ...
Alexey Romanov

66

Przeciążanie operatorów w C ++ jest uważane przez wielu za złą rzecz (tm)

Tylko przez ignorantów. Jest to absolutnie wymagane w języku takim jak C ++ i można zauważyć, że inne języki, które zaczęły przyjmować „purystyczny” pogląd, dodały go, gdy ich projektanci dowiedzieli się, jak jest to potrzebne.


30
Właściwie zgadzam się z Neilem. Przeciążanie operatorów jest niezbędne, jeśli chcesz przedstawić zmienne / stałe / obiekty / instancje jako jednostki algebraiczne ... i sprawić, by ludzie rozumieli ich interakcje w sposób matematyczny - tak powinno być w programowaniu IMHO.
Massa,

16
+1, Przeciążanie operatorów w C ++ jest dobre. Na przykład sprawia, że ​​matematyka wektorowa jest znacznie czystsza. Podobnie jak w przypadku wielu funkcji C ++, powinieneś używać tej mocy ostrożnie.
John Smith

7
@Kristo Ponieważ C ++ używa wartości, które należy przypisać i skopiować. Trzeba mieć nad tym kontrolę, więc przynajmniej trzeba umieć określić operator przypisania dla danego typu.

7
@Kristo: ponieważ jednym z celów C ++ jest umożliwienie typom zdefiniowanym przez użytkownika robienia wszystkiego, co robią typy wbudowane (chociaż są one traktowane inaczej w niektórych kontekstach, takich jak niejawne konwersje). Jeśli chcesz zaimplementować 27-bitową liczbę całkowitą, możesz to zrobić i użycie jej będzie takie samo, jak użycie int. Bez przeciążania operatorów nie byłoby możliwe użycie UDT o takiej samej składni jak typy wbudowane, a zatem język wynikowy nie byłby „podobny do C ++” w tym sensie.
Steve Jessop

8
"w ten sposób leży szaleństwo" - co gorsza, tak leży std :: vector <bool>!
Steve Jessop

42

Przeciążanie operatorów nigdy nie było powszechnie uważane za zły pomysł w C ++ - po prostu nadużywanie przeciążania operatorów było uważane za zły pomysł. Tak naprawdę nie trzeba przeciążać operatorów w języku, ponieważ i tak można je symulować za pomocą bardziej szczegółowych wywołań funkcji. Unikanie przeciążania operatorów w Javie sprawiło, że implementacja i specyfikacja Javy stała się nieco prostsza i zmusiła programistów do nie nadużywania operatorów. W społeczności Java odbyła się debata na temat wprowadzenia przeciążenia operatorów.

Zalety i wady przeciążania operatorów w Scali są takie same jak w C ++ - możesz napisać bardziej naturalny kod, jeśli odpowiednio użyjesz przeciążania operatorów - i bardziej tajemniczy, zaciemniony kod, jeśli tego nie zrobisz.

FYI: Operatory nie są zdefiniowane jako funkcje specjalne w C ++, zachowują się tak jak każda inna funkcja - chociaż istnieją pewne różnice w wyszukiwaniu nazw, czy muszą być funkcjami składowymi oraz fakt, że można je wywołać na dwa sposoby: 1 ) składnia operatora i 2) składnia identyfikatora funkcji operatora.


„Tak naprawdę nie trzeba przeciążać operatorów w języku, ponieważ i tak można je symulować za pomocą bardziej szczegółowych wywołań funkcji”. W ramach tej logiki nie potrzeba nawet operatorów . Dlaczego po prostu nie użyć add(2, multiply(5, 3))?
Joe Z.

Jest to bardziej przypadek dopasowania zwykłych używanych notacji. Weź pod uwagę matematyków i fizyków, oni potrafią zrozumieć i używać biblioteki C ++, która znacznie łatwiej zapewnia przeciążenie operatorów. Wolą raczej skoncentrować się na równaniu niż na języku programowania.
Phil Wright

19

Ten artykuł - „ Pozytywne dziedzictwo C ++ i Javy ” - odpowiada bezpośrednio na Twoje pytanie.

„C ++ ma zarówno alokację stosu, jak i alokację sterty, więc musisz przeciążać swoje operatory, aby obsłużyć wszystkie sytuacje i nie powodować wycieków pamięci. Rzeczywiście jest to trudne. Jednak Java ma pojedynczy mechanizm alokacji pamięci i moduł odśmiecania pamięci, co sprawia, że ​​przeciążanie operatora jest banalne”. ..

Java omyłkowo (zdaniem autora) pominęła przeciążenie operatorów, ponieważ w C ++ było to skomplikowane, ale zapomniała dlaczego (lub nie zdawała sobie sprawy, że nie dotyczy to Javy).

Na szczęście języki wyższego poziomu, takie jak Scala, dają programistom opcje, a jednocześnie działają na tej samej JVM.


14
Eckel jest jedynym źródłem, jakie kiedykolwiek widziałem, przedstawiającym pomysł, że przeciążanie operatorów zostało porzucone z Javy z powodu komplikacji w C ++ i nie mówi, jakie jest jego źródło. Zdyskontowałbym to. Wszystkie inne źródła, które wymieniłem, mówią, że zostało porzucone z powodu potencjalnego nadużycia. Zobacz gotw.ca/publications/c_family_interview.htm i newt.com/wohler/articles/james-gosling-ramblings-1.html . Po prostu wyszukaj je na stronie pod kątem „przeciążenia operatorów”.
James Iry

9

Nie ma nic złego w przeciążaniu operatora. W rzeczywistości, jest coś nie tak z nie mając operatora przeciążenia dla typów numerycznych. (Spójrz na kod Java, który używa BigInteger i BigDecimal.)

C ++ ma jednak tradycję nadużywania tej funkcji. Często cytowanym przykładem jest to, że operatorzy bitshift są przeciążeni, aby wykonywać operacje we / wy.


Operatory << i >> wizualnie wskazują sposób transferu, mają wykonywać operacje wejścia / wyjścia, to nie jest nadużycie, pochodzi ze standardowej biblioteki i rzeczy praktyczne. Wystarczy spojrzeć na „cin >> coś”, co się dzieje? Oczywiście od cin do czegoś.
peenut

7
@peenut: Ale ich pierwotne użycie było nieco przesunięte. „Biblioteka standardowa” używa operatora w sposób, który całkowicie miesza z oryginalną definicją.
Joe Z.

1
Jestem pewien, że gdzieś czytałem, że Bjarne Stroustrup (twórca C ++) eksperymentował z używaniem =zamiast <<i >>we wczesnych dniach C ++, ale napotkałem problemy, ponieważ nie miał on odpowiedniego pierwszeństwa operatora (tj. Czy szuka argumenty najpierw po lewej lub prawej stronie). Więc jego ręce były trochę związane z tym, czego mógłby użyć.
Phil Wright,

8

Ogólnie nie jest to zła rzecz.
Nowe języki, takie jak C #, również mają przeciążanie operatorów.

To nadużycie przeciążenia operatora jest złą rzeczą.

Ale są też problemy z przeciążaniem operatorów, jak zdefiniowano w C ++. Ponieważ przeciążone operatory są po prostu lukrem składniowym dla wywołań metod, zachowują się tak samo jak metoda. Z drugiej strony normalne operatory wbudowane nie zachowują się jak metody. Ta niespójność może powodować problemy.

Na szczycie mojej głowy operatorzy ||i &&.
Wbudowane ich wersje to operatory skrótów. Nie dotyczy to wersji przeciążonych i spowodowało pewne problemy.

Fakt, że + - * / all zwracają ten sam typ na którym działają (po promocji operatora)
Przeciążone wersje mogą zwrócić cokolwiek (tu pojawia się nadużycie, Jeśli twoi operatorzy zaczną zwracać jakiś typ arbitra którego użytkownik się nie spodziewał rzeczy idą w dół).


8

Przeciążanie operatora nie jest czymś, czego naprawdę "potrzebujesz" bardzo często, ale jeśli używasz Javy, jeśli trafisz w punkt, w którym naprawdę tego potrzebujesz, spowoduje to, że będziesz chciał wyrwać sobie paznokcie, aby mieć wymówkę, aby przestać pisać .

Ten kod, który właśnie znalazłeś, przepełnia się przez długi czas? Tak, będziesz musiał ponownie wpisać wszystko, aby działało z BigInteger. Nie ma nic bardziej frustrującego niż wymyślanie na nowo koła tylko po to, aby zmienić typ zmiennej.


6

Guy Steele argumentował, że przeciążanie operatorów powinno być również w Javie, w swoim przemówieniu zatytułowanym „Uprawianie języka” - jest tam wideo i transkrypcja tego, i to naprawdę niesamowita przemowa. Będziesz się zastanawiać, o czym on mówi przez kilka pierwszych stron, ale jeśli będziesz czytać dalej, dostrzeżesz sens i osiągniesz oświecenie. I sam fakt, że w ogóle mógł wygłosić takie przemówienie, jest również niesamowity.

Jednocześnie wykład ten zainspirował wiele podstawowych badań, prawdopodobnie w tym Scalę - to jeden z tych artykułów, które każdy powinien przeczytać, aby pracować w terenie.

Wracając do rzeczy, jego przykłady dotyczą głównie klas numerycznych (takich jak BigInteger i inne dziwne rzeczy), ale to nie jest konieczne.

Prawdą jest jednak, że niewłaściwe użycie przeciążenia operatorów może prowadzić do strasznych rezultatów, a nawet właściwe użycie może skomplikować sprawę, jeśli spróbujesz czytać kod bez odrobiny studiowania bibliotek, z których korzysta. Ale czy to dobry pomysł? OTOH, czy takie biblioteki nie powinny próbować dołączać ściągawki dla swoich operatorów?


4

Wierzę, że KAŻDA odpowiedź to pominęła. W C ++ możesz przeciążać operatory, ile chcesz, ale nie możesz wpływać na pierwszeństwo, z jakim są oceniane. Scala nie ma tego problemu, IIRC.

Co do tego, że to zły pomysł, poza kwestiami pierwszeństwa, ludzie wymyślają naprawdę głupie znaczenia dla operatorów i rzadko poprawia to czytelność. Biblioteki Scala są szczególnie złe pod tym względem, głupkowate symbole, które musisz zapamiętywać za każdym razem, a opiekunowie bibliotek chowają głowy w piasek i mówią: „trzeba się tego nauczyć tylko raz”. Świetnie, teraz muszę się nauczyć jakiejś „sprytnej” tajemniczej składni autora * liczbę bibliotek, których chcę używać. Nie byłoby tak źle, gdyby istniała konwencja ZAWSZE zapewniająca piśmienną wersję operatorów.


1
Scala również ma ustalone pierwszeństwo operatorów, prawda?
skaffman

Myślę, że jest, ale jest to znacznie bardziej płaskie. Co więcej, Scala ma mniejszy okres operatorów. +, -, * to metody, a nie operatory, IIRC. Dlatego 2 + 3 * 2, to nie 8, to 10.
Saem

7
Scala ma system pierwszeństwa oparty na pierwszym znaku symbolu. scala> 2 + 3 * 2 res0: Int = 8
James Iry

3

Przeciążanie operatorów nie było wynalazkiem C ++ - pochodzi z Algol IIRC i nawet Gosling nie twierdzi, że jest to ogólnie zły pomysł.


Jasne, ale to w swoim wcieleniu w C ++ zyskał ogólną aurę hańby.
skaffman

5
Co masz na myśli, mówiąc o „ogólnej atmosferze niesławy”? Większość osób, które znam, używa języków, które obsługują przeciążanie operatorów (C ++, C #) i nigdy nie słyszałem żadnych skarg.
Nemanja Trifunovic

Mówię o moim wieloletnim doświadczeniu z C ++ sprzed ANSI iz pewnością przypominam sobie powszechną niechęć do nich. Być może sytuacja poprawiła się dzięki ANSI C ++ lub ludzie po prostu nauczyli się, jak go nie nadużywać.
skaffman

1
Mówiąc jak ktoś, kto używa C ++ od czasów cfront (połowa lat 80-tych), mogę was zapewnić, że wprowadzenie standardu ISO nie miało wpływu na uprzedzenia ludzi dotyczące przeciążenia operatorów.

3

Jedyną nieprawidłową rzeczą w C ++ jest brak możliwości przeładowania [] = jako oddzielnego operatora. Może to być trudne do zaimplementowania w kompilatorze C ++ z prawdopodobnie nieoczywistego powodu, ale jest tego warte.


2

Jak wskazały inne odpowiedzi; Przeciążanie operatora niekoniecznie jest złe. Co jest złego, gdy jest używany w sposób, który sprawia, że ​​wynikowy kod nie jest oczywisty. Generalnie podczas ich używania trzeba sprawić, by zrobiły najmniej zaskakującą rzecz (posiadanie dzielenia operator + wykonaj spowodowałoby problemy z racjonalnym wykorzystaniem klasy) lub jak mówi Scott Meyers:

Klienci już wiedzą, jak zachowują się typy takie jak int, więc powinieneś starać się, aby twoje typy zachowywały się w ten sam sposób, gdy tylko jest to uzasadnione ... W razie wątpliwości rób to, co robią int . (Z Effective C ++ 3rd Edition, pozycja 18)

Niektórzy ludzie doprowadzili do skrajności przeciążenie operatorów za pomocą rzeczy takich jak boost :: spirit . Na tym poziomie nie masz pojęcia, jak jest zaimplementowany, ale tworzy interesującą składnię, aby zrobić to, co chcesz. Nie jestem pewien, czy to dobrze, czy źle. Wydaje się fajne, ale nie używałem go.


Nie spieram się tutaj ani za, ani przeciw przeciążeniu operatorów, nie szukam ludzi, którzy by je usprawiedliwili.
skaffman

Sprint nie zbliża się do najgorszego przykładu, z jakim się spotkałem - powinieneś zobaczyć, do czego zmierza biblioteka bazy danych RogueWave!

Zgadzam się, że Spirit nadużywa operatorów, ale nie mogę wymyślić lepszego sposobu, aby to zrobić.
Zifre

1
Nie sądzę, żeby duch nadużywał operatorów, ale popycha go. Zgadzam się, że naprawdę nie ma innego sposobu, aby to zrobić. Zasadniczo tworzy DSL w składni C ++. Bardzo daleko od tego, do czego C ++ został zaprojektowany. Tak, są dużo gorsze przykłady :) Generalnie używam ich tam, gdzie jest to właściwe. Głównie tylko operatorzy strumieniowania dla łatwiejszego debugowania \ logowania. I nawet tam jest tylko cukier, który przekazuje do metody zaimplementowanej w klasie.
Matt Price

1
To kwestia gustu; ale biblioteki kombinatorów parserów, w językach funkcyjnych, przeciążają operatory w sposób bardzo podobny do Spirit i nikt temu nie zaprzecza. Jest wiele technicznych powodów, dla których są one lepsze - Google dla „osadzonych języków specyficznych dla domeny”, aby znaleźć wiele artykułów wyjaśniających to z ogólnego punktu widzenia, a Google dla „kombinatora parsera scala” dla praktycznych przykładów w tym przypadku. Prawdą jest, że w językach funkcyjnych wynikowa składnia jest często ładniejsza - na przykład nie trzeba zmieniać znaczenia >> w celu łączenia parserów.
Blaisorblade

2

Nigdy nie widziałem artykułu stwierdzającego, że przeciążanie operatorów C ++ jest złe.

Operatory definiowane przez użytkownika pozwalają na łatwiejszy wyższy poziom ekspresji i użyteczności dla użytkowników języka.


1

Jednak jakościowo nie wyglądałoby to inaczej niż przeciążenie operatora w C ++, gdzie, jak pamiętam, operatory są definiowane jako funkcje specjalne.

AFAIK, nie ma nic specjalnego w funkcjach operatora w porównaniu do "normalnych" funkcji składowych. Oczywiście masz tylko określony zestaw operatorów, który możesz przeciążać, ale to nie czyni ich zbyt wyjątkowymi.

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.