Wiele postów narzeka na przeciążenie operatora.
Czułem, że muszę wyjaśnić pojęcia „przeciążania operatora”, oferując alternatywny punkt widzenia na tę koncepcję.
Kod zaciemnia?
Ten argument jest błędem.
Zaciemnianie jest możliwe we wszystkich językach ...
Kod w C lub Javie jest równie łatwy do zaciemnienia za pomocą funkcji / metod, jak w C ++ przez przeciążenia operatora:
// C++
T operator + (const T & a, const T & b) // add ?
{
T c ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
// Java
static T add (T a, T b) // add ?
{
T c = new T() ;
c.value = a.value - b.value ; // subtract !!!
return c ;
}
/* C */
T add (T a, T b) /* add ? */
{
T c ;
c.value = a.value - b.value ; /* subtract !!! */
return c ;
}
... Nawet w standardowych interfejsach Java
Na inny przykład zobaczmy Cloneable
interfejs w Javie:
Powinieneś sklonować obiekt implementujący ten interfejs. Ale możesz kłamać. I stwórz inny obiekt. W rzeczywistości ten interfejs jest tak słaby, że możesz zwrócić inny typ obiektu, dla zabawy:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Ponieważ Cloneable
interfejs może być nadużywany / zaciemniany, czy powinien być zbanowany z tego samego powodu, co powinno być przeciążanie operatora C ++?
Możemy przeciążyć toString()
metodę MyComplexNumber
klasy, aby zwróciła określoną godzinę dnia. Czy toString()
należy również zakazać przeciążania? Możemy sabotować, MyComplexNumber.equals
aby zwrócił losową wartość, zmodyfikował operandy ... itd. Itd. Itd.
W Javie, podobnie jak w C ++ lub w dowolnym innym języku, programista musi przestrzegać minimum semantyki podczas pisania kodu. Oznacza to zaimplementowanie add
funkcji, która dodaje i Cloneable
metodę implementacji, która klonuje, oraz ++
operatora niż inkrementy.
Co i tak zaciemnia?
Teraz, gdy wiemy, że kod może być sabotowany nawet przy użyciu nieskazitelnych metod Java, możemy zadać sobie pytanie o faktyczne wykorzystanie przeciążenia operatora w C ++?
Jasny i naturalny zapis: metody a przeciążenie operatora?
Porównamy poniżej, dla różnych przypadków, „ten sam” kod w Javie i C ++, aby zorientować się, który styl kodowania jest wyraźniejszy.
Naturalne porównania:
// C++ comparison for built-ins and user-defined types
bool isEqual = A == B ;
bool isNotEqual = A != B ;
bool isLesser = A < B ;
bool isLesserOrEqual = A <= B ;
// Java comparison for user-defined types
boolean isEqual = A.equals(B) ;
boolean isNotEqual = ! A.equals(B) ;
boolean isLesser = A.comparesTo(B) < 0 ;
boolean isLesserOrEqual = A.comparesTo(B) <= 0 ;
Należy pamiętać, że A i B mogą być dowolnego typu w C ++, o ile występują przeciążenia operatora. W Javie, gdy A i B nie są prymitywami, kod może stać się bardzo mylący, nawet w przypadku obiektów podobnych do prymitywów (BigInteger itp.) ...
Naturalne akcesoria tablic / kontenerów i indeksowanie:
// C++ container accessors, more natural
value = myArray[25] ; // subscript operator
value = myVector[25] ; // subscript operator
value = myString[25] ; // subscript operator
value = myMap["25"] ; // subscript operator
myArray[25] = value ; // subscript operator
myVector[25] = value ; // subscript operator
myString[25] = value ; // subscript operator
myMap["25"] = value ; // subscript operator
// Java container accessors, each one has its special notation
value = myArray[25] ; // subscript operator
value = myVector.get(25) ; // method get
value = myString.charAt(25) ; // method charAt
value = myMap.get("25") ; // method get
myArray[25] = value ; // subscript operator
myVector.set(25, value) ; // method set
myMap.put("25", value) ; // method put
W Javie widzimy, że dla każdego kontenera, który robi to samo (dostęp do jego zawartości poprzez indeks lub identyfikator), mamy inny sposób, aby to zrobić, co jest mylące.
W C ++ każdy kontener używa tego samego sposobu dostępu do swojej zawartości, dzięki przeciążeniu operatora.
Naturalna manipulacja typami zaawansowanymi
W poniższych przykładach użyto Matrix
obiektu znalezionego przy użyciu pierwszych linków znalezionych w Google dla „ obiektu Java Matrix ” i „obiektu C ++ Matrix ”:
// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E = A * (B / 2) ;
E += (A - B) * (C + D) ;
F = E ; // deep copy of the matrix
// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ; // deep copy of the matrix
I nie ogranicza się to do matryc. Te BigInteger
i BigDecimal
klas Javy cierpieć z tego samego mylące gadatliwości, podczas gdy ich odpowiedniki w C ++ są jasne jak wbudowanych typów.
Naturalne iteratory:
// C++ Random Access iterators
++it ; // move to the next item
--it ; // move to the previous item
it += 5 ; // move to the next 5th item (random access)
value = *it ; // gets the value of the current item
*it = 3.1415 ; // sets the value 3.1415 to the current item
(*it).foo() ; // call method foo() of the current item
// Java ListIterator<E> "bi-directional" iterators
value = it.next() ; // move to the next item & return the value
value = it.previous() ; // move to the previous item & return the value
it.set(3.1415) ; // sets the value 3.1415 to the current item
Naturalne funktory:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Łączenie tekstu:
// C++ stream handling (with the << operator)
stringStream << "Hello " << 25 << " World" ;
fileStream << "Hello " << 25 << " World" ;
outputStream << "Hello " << 25 << " World" ;
networkStream << "Hello " << 25 << " World" ;
anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ;
// Java concatenation
myStringBuffer.append("Hello ").append(25).append(" World") ;
Ok, w Javie też możesz użyć MyString = "Hello " + 25 + " World" ;
... Ale poczekaj chwilę: to przeciążenie operatora, prawda? Czy to nie oszustwo?
:-RE
Kod ogólny?
Te same ogólne operandy modyfikujące kod powinny być użyteczne zarówno dla wbudowanych / prymitywnych (które nie mają interfejsów w Javie), standardowych obiektów (które nie mogą mieć odpowiedniego interfejsu), jak i obiektów zdefiniowanych przez użytkownika.
Na przykład obliczanie średniej wartości dwóch wartości dowolnych typów:
// C++ primitive/advanced types
template<typename T>
T getAverage(const T & p_lhs, const T & p_rhs)
{
return (p_lhs + p_rhs) / 2 ;
}
int intValue = getAverage(25, 42) ;
double doubleValue = getAverage(25.25, 42.42) ;
complex complexValue = getAverage(cA, cB) ; // cA, cB are complex
Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix
// Java primitive/advanced types
// It won't really work in Java, even with generics. Sorry.
Omówienie przeciążenia operatora
Teraz, gdy widzieliśmy uczciwe porównania między kodem C ++ używającym przeciążenia operatora, a tym samym kodem w Javie, możemy teraz omówić „przeciążenie operatora” jako koncepcję.
Przeciążenie operatora istniało jeszcze przed komputerami
Nawet poza informatyki, jest przeciążanie operatorów: Na przykład w matematyce, jak operatorzy +
, -
, *
, itd. Są przeciążone.
Rzeczywiście, istotności +
, -
, *
itp zmienia się w zależności od typów argumentów (numeryczne, wektory, funkcji fali kwantowej, macierze, itp.);
Większość z nas, w ramach kursów naukowych, nauczyła się wielu znaczeń dla operatorów, w zależności od rodzaju operandów. Czy stwierdziliśmy, że są mylące?
Przeciążenie operatora zależy od jego argumentów
Jest to najważniejsza część przeciążania operatora: Podobnie jak w matematyce lub fizyce, operacja zależy od rodzaju operandów.
Więc poznaj typ operandu, a poznasz efekt operacji.
Nawet C i Java mają (zakodowane na stałe) przeciążenie operatora
W C rzeczywiste zachowanie operatora zmieni się zgodnie z operandami. Na przykład dodanie dwóch liczb całkowitych różni się od dodania dwóch liczb podwójnych lub nawet jednej liczby całkowitej i jednej liczby podwójnej. Istnieje nawet cała domena arytmetyczna wskaźnika (bez rzutowania można dodać do wskaźnika liczbę całkowitą, ale nie można dodać dwóch wskaźników ...).
W Javie nie ma arytmetyki wskaźników, ale ktoś nadal stwierdził, że konkatenacja ciągów bez +
operatora byłaby na tyle absurdalna, aby uzasadnić wyjątek w wyznaniu „przeciążanie operatora jest złe”.
Tyle, że jako programista C (z powodów historycznych) lub Java (z powodów osobistych , patrz poniżej) nie możesz podać własnego.
W C ++ przeciążenie operatora nie jest opcjonalne ...
W C ++ przeciążanie operatorów dla typów wbudowanych nie jest możliwe (i jest to dobra rzecz), ale typy zdefiniowane przez użytkownika mogą mieć przeciążenia operatora zdefiniowane przez użytkownika .
Jak już powiedziano wcześniej, w C ++ i w przeciwieństwie do Javy typy użytkowników nie są uważane za obywateli drugiej kategorii języka, w porównaniu do typów wbudowanych. Tak więc, jeśli typy wbudowane mają operatory, typy użytkowników również powinny je mieć.
Prawda jest taka, że, podobnie jak toString()
, clone()
, equals()
metody są dla Javy ( czyli quasi-norma podobny ), C ++ przeciążenie operatora jest tak samo częścią C ++, który staje się tak naturalne jak oryginalnych operatorów C lub przed wymienionych metod Javy.
W połączeniu z programowaniem szablonów przeciążenie operatora staje się dobrze znanym wzorcem projektowym. W rzeczywistości nie można posunąć się bardzo daleko w STL bez użycia przeciążonych operatorów i przeciążających operatorów dla własnej klasy.
... ale nie należy go nadużywać
Przeciążenie operatora powinno dążyć do przestrzegania semantyki operatora. Nie odejmuj w +
operatorze (jak w „nie odejmuj w add
funkcji” lub „zwracaj bzdury w clone
metodzie”).
Przeciążenie rzutów może być bardzo niebezpieczne, ponieważ może prowadzić do niejednoznaczności. Dlatego powinny być naprawdę zarezerwowane dla ściśle określonych przypadków. Jak dla &&
i ||
, nigdy nie przeciążać ich, chyba że naprawdę wiesz co robisz, jak stracisz oceny zwarcie że rodzimych operatorów &&
i ||
cieszyć się.
Więc ... Ok ... Więc dlaczego nie jest to możliwe w Javie?
Ponieważ James Gosling powiedział:
Pominąłem przeciążanie operatora jako dość osobisty wybór, ponieważ widziałem, jak zbyt wiele osób nadużywa go w C ++.
James Gosling. Źródło: http://www.gotw.ca/publications/c_family_interview.htm
Porównaj tekst Goslinga powyżej z tekstem Stroustrupa poniżej:
Wiele decyzji projektowych w C ++ ma swoje korzenie w mojej niechęci do zmuszania ludzi do robienia rzeczy w określony sposób [...] Często kusiło mnie, aby zakazać funkcji, których osobiście nie lubiłem, powstrzymywałem się od tego, ponieważ nie sądziłem, że miałem prawo do narzucania innym moich poglądów .
Bjarne Stroustrup. Źródło: Projektowanie i ewolucja C ++ (1.3 Tło ogólne)
Czy przeciążenie operatora przyniosłoby korzyści w Javie?
Niektóre obiekty znacznie skorzystałyby na przeciążeniu operatora (typy betonowe lub numeryczne, takie jak BigDecimal, liczby zespolone, macierze, kontenery, iteratory, komparatory, parsery itp.).
W C ++ możesz skorzystać z tej korzyści dzięki pokorze Stroustrupa. W Javie jesteś po prostu pieprzony z powodu osobistego wyboru Goslinga .
Czy można go dodać do Javy?
Przyczyny braku dodawania przeciążenia operatora teraz w Javie może być mieszanką wewnętrznej polityki, alergii na tę funkcję, nieufności do programistów (wiesz, sabotażyści, którzy wydają się prześladować zespoły Java ...), kompatybilności z poprzednimi maszynami JVM, czas na napisanie poprawnej specyfikacji itp.
Więc nie wstrzymuj oddechu, czekając na tę funkcję ...
Ale robią to w C # !!!
Tak...
Chociaż nie jest to jedyna różnica między tymi dwoma językami, ten nigdy mnie nie bawi.
Najwyraźniej ludzie z C #, z ich „każdym prymitywem jest struct
a struct
wywodzi się od Object” , mieli to za pierwszym razem.
Pomimo wszystkich FUD przeciwko używanemu zdefiniowanemu przeciążeniu operatora, obsługiwane są następujące języki: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...
Tak wiele języków, z tak wieloma różnymi (a czasem sprzecznymi) filozofiami, a jednak wszyscy są zgodni co do tego.
Jedzenie do przemyślenia ...