Es gibt viele Posts, die sich über die Überlastung des Bedieners beschweren.
Ich hatte das Gefühl, ich musste die Konzepte der "Operatorüberladung" klarstellen und einen alternativen Standpunkt zu diesem Konzept anbieten.
Code verschleiert?
Dieses Argument ist ein Irrtum.
Verschleierung ist in allen Sprachen möglich ...
Es ist genauso einfach, Code in C oder Java durch Funktionen / Methoden zu verschleiern wie in C ++ durch Operatorüberladungen:
// 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 ;
}
... auch in Javas Standardschnittstellen
Ein weiteres Beispiel ist die Cloneable
Benutzeroberfläche in Java:
Sie sollen das Objekt klonen, das diese Schnittstelle implementiert. Aber du könntest lügen. Und erstellen Sie ein anderes Objekt. Tatsächlich ist diese Schnittstelle so schwach, dass Sie nur zum Spaß einen anderen Objekttyp zurückgeben können:
class MySincereHandShake implements Cloneable
{
public Object clone()
{
return new MyVengefulKickInYourHead() ;
}
}
Sollte die Cloneable
Schnittstelle, da sie missbraucht / verschleiert werden kann, aus denselben Gründen gesperrt werden, aus denen eine Überladung des C ++ - Operators bestehen soll?
Wir könnten die toString()
Methode einer MyComplexNumber
Klasse überladen , damit sie die festgelegte Stunde des Tages zurückgibt. Sollte die toString()
Überladung auch verboten werden? Wir könnten sabotieren MyComplexNumber.equals
, damit es einen zufälligen Wert zurückgibt, die Operanden modifiziert ... usw. usw. usw.
In Java, wie in C ++ oder einer anderen Sprache, muss der Programmierer beim Schreiben von Code ein Minimum an Semantik beachten. Dies bedeutet, add
dass eine hinzugefügte Funktion Cloneable
und eine ++
klonende Implementierungsmethode sowie ein Operator als Inkremente implementiert werden .
Was ist überhaupt verschleiert?
Jetzt, da wir wissen, dass Code selbst mit den makellosen Java-Methoden sabotiert werden kann, können wir uns fragen, wie die Operatorüberladung in C ++ tatsächlich eingesetzt wird.
Klare und natürliche Notation: Methoden vs. Überlastung des Bedieners?
Wir werden im Folgenden für verschiedene Fälle den "gleichen" Code in Java und C ++ vergleichen, um eine Vorstellung davon zu bekommen, welche Art von Codierungsstil klarer ist.
Natürliche Vergleiche:
// 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 ;
Bitte beachten Sie, dass A und B in C ++ von jedem Typ sein können, solange die Operatorüberladungen bereitgestellt werden. Wenn in Java A und B keine Grundelemente sind, kann der Code selbst für primitivähnliche Objekte (BigInteger usw.) Sehr verwirrend werden.
Natürliche Array- / Container-Accessoren und Subskription:
// 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
In Java sehen wir, dass jeder Container, der dasselbe tut (über einen Index oder eine Kennung auf seinen Inhalt zugreift), eine andere Methode hat, was verwirrend ist.
In C ++ verwendet jeder Container dank der Überladung des Operators dieselbe Methode, um auf seinen Inhalt zuzugreifen.
Natürliche Manipulation fortgeschrittener Typen
In den folgenden Beispielen wird ein Matrix
Objekt verwendet, das mithilfe der ersten bei Google gefundenen Links für " Java Matrix-Objekt " und " C ++ Matrix-Objekt " gefunden wurde:
// 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
Und das ist nicht auf Matrizen beschränkt. Die BigInteger
und BigDecimal
Klassen von Java leiden unter der gleichen verwirrenden Ausführlichkeit, während ihre Entsprechungen in C ++ so klar sind wie die eingebauten Typen.
Natürliche Iteratoren:
// 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
Natürliche Funktoren:
// C++ Functors
myFunctorObject("Hello World", 42) ;
// Java Functors ???
myFunctorObject.execute("Hello World", 42) ;
Textverkettung:
// 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, in Java können Sie auch verwenden MyString = "Hello " + 25 + " World" ;
... Aber warten Sie eine Sekunde: Dies ist eine Überladung des Operators, nicht wahr? Betrügt es nicht ???
:-D
Generischer Code?
Dieselben generischen Code-Änderungsoperanden sollten sowohl für integrierte / primitive Elemente (die in Java keine Schnittstellen haben) als auch für Standardobjekte (die möglicherweise nicht die richtige Schnittstelle haben) und benutzerdefinierte Objekte verwendet werden können.
Berechnen Sie beispielsweise den Durchschnittswert von zwei Werten beliebiger Typen:
// 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.
Überlastung des Bedieners besprechen
Nachdem wir faire Vergleiche zwischen C ++ - Code mit Operatorüberladung und demselben Code in Java gesehen haben, können wir nun "Operatorüberladung" als Konzept diskutieren.
Operatorüberlastung bestand seit vor Computern
Auch außerhalb der Informatik gibt es Überladen von Operatoren: Zum Beispiel in der Mathematik, Operatoren wie +
, -
, *
usw. überlastet sind.
Tatsächlich ist die Bedeutung von +
, -
, *
usw. ändert sich in Abhängigkeit von den Typen der Operanden (Numerik, Vektoren, Quantenwellenfunktionen, Matrizes, etc.).
Die meisten von uns haben im Rahmen ihrer naturwissenschaftlichen Kurse je nach Art der Operanden mehrere Bedeutungen für Operatoren gelernt. Fanden wir sie verwirrend?
Die Überladung des Operators hängt von seinen Operanden ab
Dies ist der wichtigste Teil der Überladung von Operatoren: Wie in der Mathematik oder in der Physik hängt die Operation von den Typen ihrer Operanden ab.
Kennen Sie also den Typ des Operanden, und Sie kennen die Auswirkung der Operation.
Sogar C und Java haben eine (fest codierte) Operatorüberladung
In C ändert sich das tatsächliche Verhalten eines Operators entsprechend seinen Operanden. Das Hinzufügen von zwei Ganzzahlen unterscheidet sich beispielsweise vom Hinzufügen von zwei Doppelten oder sogar einer Ganzzahl und einem Doppel. Es gibt sogar die gesamte Zeigerarithmetikdomäne (ohne Umwandlung können Sie einem Zeiger eine Ganzzahl hinzufügen, aber Sie können nicht zwei Zeiger hinzufügen ...).
In Java gibt es keine Zeigerarithmetik, aber jemand, der immer noch eine Verkettung von Zeichenfolgen ohne den +
Operator gefunden hat, wäre lächerlich genug, um eine Ausnahme im Credo "Überladen von Operatoren ist böse" zu rechtfertigen.
Es ist nur so, dass Sie als C- Codierer (aus historischen Gründen) oder Java- Codierer (aus persönlichen Gründen , siehe unten) keinen eigenen bereitstellen können.
In C ++ ist das Überladen von Operatoren nicht optional ...
In C ++ ist eine Operatorüberladung für integrierte Typen nicht möglich (und dies ist eine gute Sache), aber benutzerdefinierte Typen können benutzerdefinierte Operatorüberladungen aufweisen.
Wie bereits erwähnt, werden Benutzertypen in C ++ und im Gegensatz zu Java im Vergleich zu integrierten Typen nicht als Bürger zweiter Klasse der Sprache betrachtet. Wenn integrierte Typen Operatoren haben, sollten Benutzertypen diese auch haben können.
Die Wahrheit ist , dass, wie die toString()
, clone()
, equals()
Methoden sind für Java ( dh quasi-Standard-like ), C ++ Überladen von Operatoren sind so sehr Teil von C ++ , dass es wie die Original - C - Operatoren als natürliches wird, oder die zuvor genannte Java - Methoden.
In Kombination mit der Vorlagenprogrammierung wird das Überladen von Bedienern zu einem bekannten Entwurfsmuster. Tatsächlich können Sie in STL nicht sehr weit gehen, ohne überladene Operatoren und überladene Operatoren für Ihre eigene Klasse zu verwenden.
... aber es sollte nicht missbraucht werden
Die Überladung des Bedieners sollte sich bemühen, die Semantik des Bedieners zu respektieren. Subtrahieren Sie nicht in einem +
Operator (wie in "Nicht in einer add
Funktion subtrahieren " oder "Mist in einer clone
Methode zurückgeben").
Überladung des Gipsverbandes kann sehr gefährlich sein, da sie zu Unklarheiten führen kann. Sie sollten also wirklich genau definierten Fällen vorbehalten sein. Was &&
und ||
, nicht überlastet sie nicht immer , wenn Sie wirklich wissen , was Sie tun, wie Sie die Auswertung des Kurzschlusses , dass der einheimischen Betreiber verlieren würden &&
und ||
genießen.
Also ... Ok ... warum ist es dann in Java nicht möglich?
Weil James Gosling es gesagt hat:
Ich habe das Überladen von Operatoren als ziemlich persönliche Wahl ausgelassen, weil ich zu viele Leute gesehen habe, die es in C ++ missbraucht haben.
James Gosling. Quelle: http://www.gotw.ca/publications/c_family_interview.htm
Bitte vergleichen Sie Goslings Text oben mit Stroustrups unten:
Viele C ++ - Entwurfsentscheidungen haben ihre Wurzeln in meiner Abneigung, Menschen dazu zu zwingen, Dinge auf eine bestimmte Art und Weise zu tun. [...] Oft war ich versucht, eine Funktion zu verbieten, die ich persönlich nicht mochte. Ich verzichtete darauf, weil ich nicht glaubte, dass ich sie hatte das Recht, meine Ansichten anderen aufzuzwingen .
Bjarne Stroustrup. Quelle: Das Design und die Entwicklung von C ++ (1.3 Allgemeiner Hintergrund)
Würde eine Überlastung des Bedieners Java zugute kommen?
Einige Objekte würden stark von einer Überladung des Operators profitieren (konkrete oder numerische Typen wie BigDecimal, komplexe Zahlen, Matrizen, Container, Iteratoren, Komparatoren, Parser usw.).
In C ++ können Sie aufgrund der Demut von Stroustrup von diesem Vorteil profitieren. In Java werden Sie einfach wegen Goslings persönlicher Wahl verarscht .
Könnte es zu Java hinzugefügt werden?
Die Gründe dafür, dass in Java keine Operatorüberlastung hinzugefügt wird, könnten eine Mischung aus interner Politik, Allergie gegen die Funktion, Misstrauen gegenüber Entwicklern (Sie wissen, die Saboteure, die Java-Teams zu verfolgen scheinen ...) und Kompatibilität mit den vorherigen JVMs sein. Zeit, eine korrekte Spezifikation zu schreiben, etc ..
Halten Sie also nicht den Atem an und warten Sie auf diese Funktion ...
Aber sie machen es in C # !!!
Ja...
Während dies bei weitem nicht der einzige Unterschied zwischen den beiden Sprachen ist, amüsiert mich diese immer wieder.
Anscheinend haben die C # -Leute mit ihrem "jedes Primitiv ist ein struct
und ein struct
stammt von Object" es beim ersten Versuch richtig gemacht.
Trotz aller FUD gegen das Überladen definierter Operatoren wird es von folgenden Sprachen unterstützt: 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 ...
So viele Sprachen mit so vielen verschiedenen (und manchmal gegensätzlichen) Philosophien, und doch sind sich alle in diesem Punkt einig.
Denkanstöße ...