Ich habe gerade einen Artikel von Joel gelesen, in dem er sagt:
Im Allgemeinen muss ich zugeben, dass ich ein bisschen Angst vor Sprachfeatures habe, die Dinge verbergen . Wenn Sie den Code sehen
i = j * 5;
… In C wissen Sie zumindest, dass j mit fünf multipliziert und die Ergebnisse in i gespeichert werden.
Wenn Sie jedoch denselben Codeausschnitt in C ++ sehen, wissen Sie nichts. Nichts. Die einzige Möglichkeit, um zu wissen, was in C ++ wirklich passiert, besteht darin, herauszufinden, welche Typen i und j sind, was möglicherweise an einer anderen Stelle deklariert wird. Das liegt daran, dass j möglicherweise von einem Typ ist, der
operator*
überladen ist und schrecklich witzig ist, wenn Sie versuchen, es zu multiplizieren.
(Hervorheben von mir.) Angst vor Sprachmerkmalen, die Dinge verbergen? Wie können Sie sich davor fürchten? Ist das Verstecken von Dingen (auch als Abstraktion bekannt ) nicht eine der Schlüsselideen der objektorientierten Programmierung? Jedes Mal, wenn Sie eine Methode aufrufen a.foo(b)
, haben Sie keine Ahnung, was dies bewirken könnte. Sie müssen herausfinden, welche Typen a
und welche Typen es b
gibt, etwas, das an einer anderen Stelle deklariert werden könnte. Sollen wir also die objektorientierte Programmierung abschaffen, weil sie dem Programmierer zu viele Dinge verbirgt?
Und was j * 5
unterscheidet sich davon j.multiply(5)
, dass Sie möglicherweise in einer Sprache schreiben müssen, die das Überladen von Operatoren nicht unterstützt? Auch hier müssten Sie die Art j
der multiply
Methode herausfinden und einen Blick in sie werfen, denn siehe da, sie j
könnte von einer Art sein, die eine multiply
Methode hat, die etwas furchtbar Witziges tut.
"Muahaha, ich bin ein böser Programmierer, der eine Methode benennt multiply
, aber was sie tatsächlich tut, ist völlig undurchsichtig und nicht intuitiv und hat überhaupt nichts damit zu tun, Dinge zu multiplizieren." Ist das ein Szenario, das wir beim Entwerfen einer Programmiersprache berücksichtigen müssen? Dann müssen wir die Bezeichner aus den Programmiersprachen weglassen, weil sie irreführend sein könnten!
Wenn Sie wissen möchten, was eine Methode bewirkt, können Sie entweder einen Blick auf die Dokumentation werfen oder einen Blick in die Implementierung werfen. Das Überladen von Operatoren ist nur syntaktischer Zucker, und ich verstehe nicht, wie es das Spiel überhaupt verändert.
Bitte erleuchte mich.
Antworten:
Die Abstraktion „verbirgt“ Code, sodass Sie sich nicht um das Innenleben kümmern müssen und diese häufig nicht ändern können, aber Sie sollten nicht daran gehindert werden, sich den Code anzusehen. Wir machen nur Annahmen über die Betreiber und wie Joel sagte, könnte es überall sein. Wenn Sie eine Programmierfunktion haben, für die alle überlasteten Operatoren an einem bestimmten Ort eingerichtet sein müssen, kann dies hilfreich sein, aber ich bin mir nicht sicher, ob die Verwendung dadurch einfacher wird.
Ich sehe nicht, dass man * dazu bringt, etwas zu tun, das der Multiplikation nicht besser ähnelt als eine Funktion namens Get_Some_Data, die Daten löscht.
quelle
<<
Operator für Streams definiert, der nichts mit der bitweisen Verschiebung zu tun hat, direkt in der Standardbibliothek von C ++.Meiner Meinung nach bieten Sprachfunktionen wie das Überladen von Operatoren dem Programmierer mehr Leistung. Und wie wir alle wissen, geht mit großer Kraft große Verantwortung einher. Funktionen, die Ihnen mehr Kraft verleihen, bieten Ihnen auch mehr Möglichkeiten, sich in den Fuß zu schießen, und sollten natürlich mit Bedacht eingesetzt werden.
Zum Beispiel ist es absolut sinnvoll,
+
den*
Operatorclass Matrix
oder für oder zu überlastenclass Complex
. Jeder wird sofort wissen, was es bedeutet. Andererseits ist für mich die Tatsache, dass+
die Verkettung von Zeichenfolgen bedeutet, überhaupt nicht offensichtlich, obwohl Java dies als Teil der Sprache tut, und STL dies für diestd::string
Verwendung der Überladung von Operatoren tut .Ein weiteres gutes Beispiel für das Überladen von Operatoren, um den Code deutlicher zu machen, sind intelligente Zeiger in C ++. Sie möchten, dass sich die intelligenten Zeiger so weit wie möglich wie normale Zeiger verhalten. Daher ist es sinnvoll, die unären
*
und->
Operatoren zu überlasten .Im Wesentlichen ist das Überladen von Operatoren nichts anderes als eine andere Art, eine Funktion zu benennen. Und es gibt eine Regel für die Benennung von Funktionen: Der Name muss beschreibend sein, damit sofort ersichtlich ist, was die Funktion tut. Die gleiche exakte Regel gilt für das Überladen des Bedieners.
quelle
*
dieser Vektoren kann daher zu Verwirrung führen. Möglicherweise könnten Sieoperator*()
für das Skalarprodukt undoperator%()
für das Kreuzprodukt verwenden, aber ich würde das nicht für eine allgemein verwendbare Bibliothek tun.A-B
alsB-A
entweder, und alle Betreiber folgen diesem Muster. Obwohl es immer eine Ausnahme gibt: Wenn der Compiler beweisen kann, dass es keine Rolle spielt, darf er alles neu anordnen.In Haskell sind "+", "-", "*", "/" usw. nur (Infix-) Funktionen.
Sollten Sie eine Infix-Funktion "plus" wie in "4 plus 2" benennen? Warum nicht, wenn Addition Ihre Funktion ist. Sollten Sie Ihre "Plus" -Funktion "+" nennen? Warum nicht.
Ich denke, das Problem bei sogenannten "Operatoren" ist, dass sie meistens mathematischen Operationen ähneln und es nicht viele Möglichkeiten gibt, diese zu interpretieren, und daher werden hohe Erwartungen an das, was eine solche Methode / Funktion / ein solcher Operator tut, gestellt.
EDIT: machte meinen Standpunkt klarer
quelle
int
,float
,long long
und was auch immer. Worum geht es also?+
für verschiedene Typen integrierter Nummern zu verwenden, sondern benutzerdefinierte Überladungen zu erstellen. daher mein Kommentar.Aufgrund der anderen Antworten, die ich gesehen habe, kann ich nur den Schluss ziehen, dass der eigentliche Einwand gegen das Überladen von Operatoren der Wunsch nach sofort offensichtlichem Code ist.
Dies ist aus zwei Gründen tragisch:
quelle
Ich stimme etwas zu.
Wenn Sie schreiben
multiply(j,5)
, kann esj
sich um einen Skalar- oder Matrixtyp handelnmultiply()
, der je nach dem, wasj
ist , mehr oder weniger komplex ist. Wenn Sie jedoch die Idee des Überladens ganz aufgeben, müsste die Funktion benannt werden,multiply_scalar()
odermultiply_matrix()
was würde es offensichtlich machen, was darunter passiert.Es gibt Code, bei dem viele von uns den einen Weg bevorzugen, und es gibt Code, bei dem die meisten von uns den anderen Weg bevorzugen. Der größte Teil des Codes liegt jedoch in der Mitte zwischen diesen beiden Extremen. Was Sie dort bevorzugen, hängt von Ihrem Hintergrund und Ihren persönlichen Vorlieben ab.
quelle
multiply_matrix()
auch keine generische Programmierung mögen.real_multiply()
oder so erwarten . Entwickler sind oft nicht gut mit Namen und werdenoperator*()
zumindest konsequent sein.operator*()
das etwas Dummesj
ist, ein Makro, das zu einem Ausdruck ausgewertet wird, der fünf Funktionsaufrufe umfasst, und so weiter. Dann kann man die beiden Ansätze nicht mehr vergleichen. Aber, ja, es ist schwer, die Dinge gut zu benennen, obwohl es sich lohnt, wie lange es dauert.Ich sehe zwei Probleme mit der Überladung des Operators.
&&
,||
oder,
, verlieren Sie die Sequenzpunkte, die von den Einbauvarianten dieser Operatoren impliziert sind (wie auch die Kurzschlüsse Verhalten der logischen Operatoren). Aus diesem Grund ist es besser, diese Operatoren nicht zu überladen, auch wenn die Sprache dies zulässt.operator<<
Streams.quelle
&&
und||
in einer Weise zuzulassen , die keine Sequenzierung impliziert, war ein großer Fehler (IMHO, wenn C ++ eine Überladung dieser zulassen würde, hätte es ein spezielles "Zwei-Funktionen" -Format verwenden müssen, wobei die erste Funktion erforderlich ist Um einen implizit in eine Ganzzahl umwandelbaren Typ zurückzugeben, kann die zweite Funktion zwei oder drei Argumente annehmen, wobei das "zusätzliche" Argument der zweiten Funktion der Rückgabetyp der ersten ist. Der Compiler würde die erste Funktion aufrufen und dann Wenn es nicht nullfoo.bar[3].X
vonfoo
der Klasse behandelt wird, anstatt dassfoo
ein Member verfügbar gemacht werden muss könnte Subskription unterstützen und dann ein Mitglied verfügbar machenX
. Wenn man die Auswertung über den tatsächlichen Mitgliederzugriff erzwingen möchte, würde man schreiben((foo.bar)[3]).X
.Aufgrund meiner persönlichen Erfahrung bedeutet die Java-Methode, mehrere Methoden zuzulassen, aber den Operator nicht zu überladen, dass Sie genau wissen , was ein Operator tut , wenn Sie ihn sehen .
Sie müssen nicht feststellen, ob
*
fremder Code aufgerufen wird, sondern müssen wissen, dass es sich um eine Multiplikation handelt. Das Verhalten entspricht genau der Definition in der Java-Sprachspezifikation. Dies bedeutet, dass Sie sich auf das tatsächliche Verhalten konzentrieren können, anstatt alle vom Programmierer definierten Wickets zu finden.Mit anderen Worten, das Verhindern von Bedienerüberlastungen ist ein Vorteil für den Leser und nicht für den Schreiber und erleichtert daher die Wartung von Programmen!
quelle
list.get(n)
Syntax leben?std::list
überladen nichtoperator[]
(oder geben keine anderen Indizierungsmöglichkeiten für die Liste an), da eine solche Operation O (n) wäre und eine Listenschnittstelle eine solche Funktion nicht verfügbar machen sollte, wenn Sie Wert auf Effizienz legen. Clients könnten versucht sein, über verknüpfte Listen mit Indizes zu iterieren, was O (n) -Algorithmen unnötigerweise zu O (n ^ 2) macht. Sie sehen das ziemlich oft in Java-Code, besonders wenn Leute mit derList
Schnittstelle arbeiten, die darauf abzielt, die Komplexität vollständig wegzuziehen.time.add(anotherTime)
, müssen Sie auch überprüfen, ob der Bibliotheks-Programmierer die Add-Operation "korrekt" implementiert hat (was auch immer das bedeutet).Ein Unterschied zwischen Überlastung
a * b
und Aufrufenmultiply(a,b)
besteht darin, dass letzteres leicht erkannt werden kann. Wenn diemultiply
Funktion für verschiedene Typen nicht überladen ist, können Sie genau herausfinden, was die Funktion tun wird, ohne die Typen vona
und nachverfolgen zu müssenb
.Linus Torvalds hat ein interessantes Argument zur Überlastung von Operatoren. In so etwas wie der Linux-Kernel-Entwicklung, in der die meisten Änderungen per E-Mail über Patches gesendet werden, ist es wichtig, dass die Betreuer verstehen, was ein Patch mit nur wenigen Kontextzeilen um jede Änderung macht. Wenn Funktionen und Operatoren nicht überlastet sind, kann der Patch einfacher kontextunabhängig gelesen werden, da Sie nicht die geänderte Datei durchgehen müssen, um alle Typen zu ermitteln und nach überlasteten Operatoren zu suchen.
quelle
Ich vermute, es hat etwas damit zu tun, die Erwartungen zu brechen. Ich bin an C ++ gewöhnt, Sie sind es gewohnt, dass das Verhalten von Operatoren nicht ausschließlich von der Sprache bestimmt wird, und Sie werden nicht überrascht sein, wenn ein Operator etwas Seltsames tut. Wenn Sie an Sprachen ohne diese Funktion gewöhnt sind und dann C ++ - Code sehen, bringen Sie die Erwartungen dieser anderen Sprachen mit und sind möglicherweise böse überrascht, wenn Sie feststellen, dass ein überladener Operator etwas Unkonventionelles tut.
Persönlich denke ich, dass es einen Unterschied gibt. Wenn Sie das Verhalten der eingebauten Syntax der Sprache ändern können, wird es undurchsichtiger. Sprachen, die keine Metaprogrammierung erlauben, sind syntaktisch weniger leistungsfähig, aber konzeptionell einfacher zu verstehen.
quelle
Ich denke, dass das Überladen von mathematischen Operatoren nicht das eigentliche Problem beim Überladen von Operatoren in C ++ ist. Ich halte das Überladen von Operatoren, die sich nicht auf den Kontext des Ausdrucks (dh Typ) verlassen sollten, für "böse". ZB Überladung
,
[ ]
( )
->
->*
new
delete
oder gar die Einzahl*
. Sie haben bestimmte Erwartungen an die Betreiber, die sich niemals ändern sollten.quelle
operator[]
, Funktoren ohneoperator()
, intelligente Zeiger ohneoperator->
und so weiter nicht gerne sehen würde .[]
sollte immer ein Array-ähnlicher Accessor sein und->
immer den Zugriff auf ein Mitglied bedeuten. Es spielt keine Rolle, ob es sich tatsächlich um ein Array oder einen anderen Container handelt oder ob es sich um einen intelligenten Zeiger handelt oder nicht.Ich verstehe, dass Sie Joels Argument über das Verstecken nicht mögen. Ich auch nicht. Es ist in der Tat viel besser, '+' für Dinge wie eingebaute numerische Typen oder für eigene wie zum Beispiel Matrix zu verwenden. Ich gebe zu, dass dies ordentlich und elegant ist, um zwei Matrizen mit dem '*' anstelle von '.multiply ()' multiplizieren zu können. Und schließlich haben wir in beiden Fällen die gleiche Art von Abstraktion.
Was hier weh tut, ist die Lesbarkeit Ihres Codes. In realen Fällen nicht im akademischen Beispiel der Matrixmultiplikation. Insbesondere, wenn Ihre Sprache es ermöglicht, Operatoren zu definieren, die zum Beispiel anfangs nicht im Sprachkern vorhanden sind
=:=
. An dieser Stelle stellen sich viele zusätzliche Fragen. Worum geht es bei diesem verdammten Operator? Ich meine, was ist der Vorrang dieses Dings? Was ist die Assoziativität? In welcher Reihenfolge wird dasa =:= b =:= c
wirklich ausgeführt?Das ist schon ein Argument gegen das Überladen von Operatoren. Immer noch nicht überzeugt? Das Überprüfen der Vorrangregeln hat nicht länger als 10 Sekunden gedauert? Ok, lass uns weiter gehen.
Wenn Sie anfangen, eine Sprache zu verwenden, die das Überladen von Operatoren ermöglicht, beispielsweise die populäre Sprache, deren Name mit 'S' beginnt, werden Sie schnell feststellen, dass Bibliotheksentwickler es lieben, Operatoren zu überschreiben. Natürlich sind sie gut ausgebildet, folgen den Best Practices (kein Zynismus hier) und alle ihre APIs sind vollkommen sinnvoll, wenn wir sie getrennt betrachten.
Stellen Sie sich nun vor, Sie müssen einige APIs verwenden, bei denen die Operatoren, die zusammen in einem einzigen Codeteil überladen werden, stark genutzt werden. Oder noch besser - Sie müssen so einen alten Code lesen. Dies ist der Zeitpunkt, an dem die Bedienerüberladung wirklich zum Kotzen kommt. Grundsätzlich, wenn es viele überladene Operatoren an einer Stelle gibt, werden sie sich bald mit den anderen nicht-alphanumerischen Zeichen in Ihrem Programmcode vermischen. Sie mischen sich mit nicht-alphanumerischen Zeichen, die eigentlich keine Operatoren sind, sondern eher grundlegende sprachliche Grammatikelemente, die Dinge wie Blöcke und Bereiche definieren, Anweisungen zur Formflusssteuerung oder einige Metadinge bezeichnen. Sie müssen die Brille aufsetzen und Ihre Augen 10 cm näher an das LCD-Display heranführen, um das visuelle Durcheinander zu verstehen.
quelle
Im Allgemeinen vermeide ich das Überladen von Operatoren auf nicht intuitive Weise. Das heißt, wenn ich eine numerische Klasse habe, ist das Überladen von * akzeptabel (und wird empfohlen). Was würde eine Überlastung * jedoch bewirken, wenn ich einen Klassenmitarbeiter hätte? Mit anderen Worten: Überladen Sie Operatoren auf intuitive Weise, um das Lesen und Verstehen zu vereinfachen.
Akzeptabel / empfohlen:
Inakzeptabel:
quelle
Zusätzlich zu dem, was hier bereits gesagt wurde, gibt es noch ein Argument gegen das Überladen von Operatoren. In der Tat, wenn Sie schreiben
+
, ist dies ein bisschen offensichtlich, dass Sie das Hinzufügen von etwas zu etwas bedeuten. Dies ist jedoch nicht immer der Fall.C ++ selbst bietet ein großartiges Beispiel für einen solchen Fall. Wie
stream << 1
soll gelesen werden? Stream um 1? nach links verschoben Es ist überhaupt nicht offensichtlich, es sei denn, Sie wissen ausdrücklich, dass << in C ++ auch in den Stream schreibt. Wenn diese Operation jedoch als Methode implementiert wäreo.leftShift(1)
, würde kein vernünftiger Entwickler schreiben , es wäre so etwas wieo.write(1)
.Das Fazit ist, dass die Programmiersprache durch die Nichtverfügbarkeit der Operatorüberladung die Programmierer zum Nachdenken über die Namen der Operationen anregt. Auch wenn der gewählte Name nicht perfekt ist, ist es immer noch schwieriger, einen Namen falsch zu interpretieren als ein Zeichen.
quelle
Im Vergleich zu buchstabierten Methoden sind Operatoren kürzer, erfordern jedoch keine Klammern. Klammern sind relativ unbequem zu tippen. Und du musst sie ausbalancieren. Insgesamt erfordert jeder Methodenaufruf im Vergleich zu einem Operator drei Zeichen für einfaches Rauschen. Dies macht die Verwendung von Operatoren sehr, sehr verlockend.
Warum sollte sonst jemand das wollen
cout << "Hello world"
:?Das Problem mit Überladung ist, dass die meisten Programmierer unglaublich faul sind und sich die meisten Programmierer das nicht leisten können.
Was C ++ - Programmierer zum Missbrauch der Überladung von Operatoren antreibt, ist nicht das Vorhandensein, sondern das Fehlen einer besseren Möglichkeit, Methodenaufrufe auszuführen. Und die Leute haben nicht nur Angst vor einer Überlastung der Bediener, weil dies möglich ist, sondern auch, weil dies geschehen ist.
Beachten Sie, dass zum Beispiel in Ruby und Scala niemand Angst vor einer Überlastung der Bediener hat. Abgesehen davon, dass die Verwendung von Operatoren nicht wirklich kürzer ist als die von Methoden, liegt ein weiterer Grund darin, dass Ruby die Überladung von Operatoren auf ein vernünftiges Minimum beschränkt, während Sie in Scala Ihre eigenen Operatoren deklarieren können , um Kollisionen zu vermeiden.
quelle
Der Grund, warum das Überladen von Operatoren beängstigend ist, ist, dass es eine große Anzahl von Programmierern gibt, die niemals DENKEN würden, was
*
nicht einfach "multiplizieren" bedeutet, während eine Methode wiefoo.multiply(bar)
diese dem Programmierer zumindest augenblicklich darauf hinweist, dass jemand eine benutzerdefinierte Multiplikationsmethode geschrieben hat . An welchem Punkt würden sie sich fragen warum und nachforschen gehen.Ich habe mit "guten Programmierern" zusammengearbeitet, die sich in höheren Positionen befanden und Methoden mit dem Namen "CompareValues" erstellten, die zwei Argumente verwendeten und die Werte von einem zum anderen anwendeten und einen Booleschen Wert zurückgaben. Oder eine Methode namens "LoadTheValues", die für 3 andere Objekte in die Datenbank geht, Werte abruft, Berechnungen durchführt, diese ändert
this
und in der Datenbank speichert.Wenn ich in einem Team mit solchen Programmierern arbeite, weiß ich sofort, woran sie gearbeitet haben. Wenn sie einen Bediener überlastet haben, habe ich überhaupt keine Möglichkeit zu wissen, dass sie es getan haben, außer anzunehmen, dass sie es getan haben und auf die Suche gehen.
In einer perfekten Welt oder einem Team mit perfekten Programmierern ist das Überladen von Operatoren wahrscheinlich ein fantastisches Werkzeug. Ich muss allerdings noch an einem Team perfekter Programmierer arbeiten, deshalb ist es beängstigend.
quelle