Ich verstehe die Argumente gegen das Überladen von Operatoren nicht


82

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 aund welche Typen es bgibt, 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 * 5unterscheidet 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 jder multiplyMethode herausfinden und einen Blick in sie werfen, denn siehe da, sie jkönnte von einer Art sein, die eine multiplyMethode 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.


20
+1: Gut geschrieben, gut argumentiert, interessantes Thema und sehr umstritten. Ein leuchtendes Beispiel für eine p.se-Frage.
Allon Guralnek

19
+1: Die Leute hören Joel Spolsky, weil er gut schreibt und bekannt ist. Aber das macht ihn nicht 100% der Zeit richtig. Ich stimme Ihrem Argument zu. Wenn wir hier alle Joels Logik folgen würden, würden wir nie etwas erreichen.
Niemand

5
Ich würde argumentieren, dass entweder i und j lokal deklariert werden, damit Sie ihren Typ schnell sehen können, oder sie sind blöde Variablennamen und sollten entsprechend umbenannt werden.
Cameron MacFarland

5
+1, aber vergessen Sie nicht den größten Teil von Joels Artikel: Nachdem er einen Marathon in Richtung der richtigen Antwort gelaufen ist, bleibt er ohne ersichtlichen Grund 50 Fuß daneben stehen. Falscher Code sollte nicht falsch aussehen. es sollte nicht kompiliert werden.
Larry Coleman

3
@ Larry: Sie können dafür sorgen, dass falscher Code nicht kompiliert wird, indem Sie Klassen entsprechend definieren. In seinem Beispiel könnten Sie also SafeString und UnsafeString in C ++ oder RowIndex und ColumnIndex verwenden, aber Sie müssten dann das Überladen von Operatoren verwenden, damit sie sich intuitiv verhalten.
David Thornley

Antworten:


32

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.


13
+1 Für das Bit 'Ich sehe nicht'. Sprachfeatures sind zur Verwendung da, nicht zum Missbrauch.
Michael K

5
Wir haben jedoch einen <<Operator für Streams definiert, der nichts mit der bitweisen Verschiebung zu tun hat, direkt in der Standardbibliothek von C ++.
Malcolm

Der Operator "bitweise Verschiebung" wird nur aus historischen Gründen als "bitweise Verschiebung" bezeichnet. Bei der Anwendung auf Standardtypen erfolgt eine bitweise Verschiebung (auf dieselbe Weise, wie der Operator + bei der Anwendung auf numerische Typen Zahlen addiert). Bei der Anwendung auf einen komplexen Typ kann er jedoch das tun, was er will, solange er es tut Sinn für diesen Typ.
Gbjbaanb

1
* wird auch für die Dereferenzierung verwendet (wie bei intelligenten Zeigern und Iteratoren). Es ist nicht klar, wo die Grenze zwischen guter und schlechter Überladung zu setzen ist
Martinkunev

Es wäre nicht irgendwo, es wäre in der Typdefinition von j.
Andy

19

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 *Operator class Matrixoder für oder zu überlasten class 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 die std::stringVerwendung 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.


1
Die letzten beiden Sätze bringen den Einwand gegen das Überladen von Operatoren auf den Punkt: Der Wunsch, dass der gesamte Code sofort offensichtlich wird.
Larry Coleman

2
Ist es nicht offensichtlich, was M * N bedeutet, wobei M und N vom Typ Matrix sind?
Dima

2
@Fred: Nein. Es gibt eine Art der Matrixmultiplikation. Sie können eine mxn-Matrix mit einer nxk-Matrix multiplizieren und eine mxk-Matrix erhalten.
Dima

1
@FredOverflow: Es gibt verschiedene Möglichkeiten, einen dreidimensionalen Vektor zu multiplizieren, wobei einer einen Skalar und einer einen anderen dreidimensionalen Vektor ergibt. Eine Überladung *dieser Vektoren kann daher zu Verwirrung führen. Möglicherweise könnten Sie operator*()für das Skalarprodukt und operator%()für das Kreuzprodukt verwenden, aber ich würde das nicht für eine allgemein verwendbare Bibliothek tun.
David Thornley

2
@ Martin Beckett: Nein C ++ ist nicht neu anordnen dürfen A-Bals B-Aentweder, 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.
Sjoerd

9

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


Ähm, abgesehen von dem, was von C geerbt wurde, macht C ++ (und danach hat Fred gefragt) so ziemlich dasselbe. Nun, wie sehen Sie das, ob es gut oder schlecht ist?
sbi

@sbi Ich liebe Betreiber Überlastung ... Eigentlich sogar C hat überladene Operatoren ... Sie sie für verwenden können int, float, long longund was auch immer. Worum geht es also?
FUZxxl

@FUZxxl: Hier geht es darum, dass benutzerdefinierte Operatoren die eingebauten überladen.
sbi

1
@sbi Haskell unterscheidet nicht zwischen eingebaut und benutzerdefiniert . Alle Operatoren sind gleich. Sie können sogar einige Erweiterungen aktivieren, mit denen Sie alle vordefinierten Elemente entfernen und alles von Grund auf neu schreiben können, einschließlich aller Operatoren.
FUZxxl

@FUZxxl: Das mag gut sein, aber diejenigen, die überladene Operatoren ablehnen, lehnen es normalerweise nicht ab, Built-In +für verschiedene Typen integrierter Nummern zu verwenden, sondern benutzerdefinierte Überladungen zu erstellen. daher mein Kommentar.
sbi

7

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:

  1. Das Prinzip, dass Code sofort offensichtlich sein sollte, lässt uns alle nach wie vor in COBOL codieren.
  2. Sie lernen nicht aus Code, der sofort offensichtlich ist. Sie lernen aus sinnvollem Code, wenn Sie sich etwas Zeit nehmen, um zu überlegen, wie er funktioniert.

Aus Code zu lernen ist jedoch nicht immer das Hauptziel. In einem Fall wie "Feature X ist fehlerhaft, die Person, die es geschrieben hat, hat das Unternehmen verlassen, und Sie müssen es so schnell wie möglich reparieren", hätte ich lieber Code, der sofort offensichtlich ist.
Errorsatz

5

Ich stimme etwas zu.

Wenn Sie schreiben multiply(j,5), kann es jsich um einen Skalar- oder Matrixtyp handeln multiply(), der je nach dem, was jist , mehr oder weniger komplex ist. Wenn Sie jedoch die Idee des Überladens ganz aufgeben, müsste die Funktion benannt werden, multiply_scalar()oder multiply_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.


Guter Punkt. Das Aufgeben der Überladung insgesamt ist jedoch mit der generischen Programmierung nicht gut zu
vereinbaren

@FredO: Natürlich nicht. Bei der generischen Programmierung geht es jedoch nur darum, den gleichen Algorithmus für sehr unterschiedliche Typen zu verwenden, sodass diejenigen, die es bevorzugen, multiply_matrix()auch keine generische Programmierung mögen.
sbi

2
Du bist ziemlich optimistisch in Bezug auf Namen, nicht wahr? Aufgrund einiger Orte, an denen ich gearbeitet habe, würde ich Namen wie "multiply ()" und "multiplym ()" oder vielleicht real_multiply()oder so erwarten . Entwickler sind oft nicht gut mit Namen und werden operator*()zumindest konsequent sein.
David Thornley

@ David: Ja, ich habe die Tatsache übersprungen, dass die Namen vielleicht schlecht sind. Aber wir könnten genauso gut davon ausgehen, dass operator*()das etwas Dummes jist, 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.
sbi

5
@David: Und da es schwierig ist, Namen zu benennen, sollten sie aus den Programmiersprachen verbannt werden, oder? Es ist einfach zu einfach, sie falsch zu verstehen! ;-)
fredoverflow

4

Ich sehe zwei Probleme mit der Überladung des Operators.

  1. Durch das Überladen wird die Semantik des Operators geändert, auch wenn dies vom Programmierer nicht beabsichtigt ist. Zum Beispiel, wenn Sie überlasten &&, ||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.
  2. Einige Leute betrachten das Überladen von Operatoren als eine so nette Funktion, dass sie es überall einsetzen, auch wenn es nicht die geeignete Lösung ist. Dies führt dazu, dass andere Personen in die andere Richtung überreagieren und vor einer Überlastung des Bedieners warnen. Ich bin mit keiner der beiden Gruppen einverstanden, gehe aber den Mittelweg: Das Überladen von Operatoren sollte sparsam und nur dann eingesetzt werden, wenn
    • Der überladene Operator hat die natürliche Bedeutung sowohl für die Domain-Experten als auch für die Software-Experten. Wenn sich diese beiden Gruppen nicht auf die natürliche Bedeutung für den Bediener einigen, überlasten Sie sie nicht.
    • für den / die betroffenen Typ (en) gibt es keine natürliche Bedeutung für den Operator und der unmittelbare Kontext (vorzugsweise derselbe Ausdruck, jedoch nicht mehr als einige Zeilen) macht immer klar, welche Bedeutung der Operator hat. Ein Beispiel für diese Kategorie wäre operator<<Streams.

1
+1 von mir, aber das zweite Argument kann genauso gut auf die Vererbung angewendet werden. Viele Menschen haben keine Ahnung von Vererbung und versuchen, sie auf alles anzuwenden. Ich denke, die meisten Programmierer würden zustimmen, dass es möglich ist, Vererbung zu missbrauchen. Bedeutet das, dass Vererbung "böse" ist und von den Programmiersprachen abgewichen werden sollte? Oder sollten wir es so belassen, weil es auch nützlich sein kann?
Fredoverflow

@FredOverflow Das zweite Argument kann auf alles angewendet werden, was "neu und heiß" ist. Ich gebe es nicht als Argument an, das Überladen von Operatoren aus einer Sprache zu entfernen, sondern als Grund, warum Leute dagegen argumentieren. Aus meiner Sicht ist eine Überladung des Bedieners nützlich, sollte aber mit Vorsicht angewendet werden.
Bart van Ingen Schenau

IMHO, Überladungen von &&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 null
zurückgibt, werte

Das ist natürlich nicht annähernd so bizarr wie die Überladung des Komma-Operators. Übrigens wäre eine überladende Sache, die ich nicht wirklich gesehen habe, aber gerne gesehen hätte, ein Mittel, um den Zugriff auf Mitglieder zu verschärfen, indem ein Ausdruck wie foo.bar[3].Xvon fooder Klasse behandelt wird, anstatt dass fooein Member verfügbar gemacht werden muss könnte Subskription unterstützen und dann ein Mitglied verfügbar machen X. Wenn man die Auswertung über den tatsächlichen Mitgliederzugriff erzwingen möchte, würde man schreiben ((foo.bar)[3]).X.
Supercat

3

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!


+1, mit einer Einschränkung: C ++ gibt Ihnen genug Seil, um sich zu erhängen. Wenn ich jedoch eine verknüpfte Liste in C ++ implementieren möchte, möchte ich die Möglichkeit haben, mit [] auf das n-te Element zuzugreifen. Es ist sinnvoll, die Operatoren für Daten zu verwenden, für die sie (mathematisch gesehen) gültig sind.
Michael K

@Michael, kannst du nicht mit der list.get(n)Syntax leben?

@ Thorbjørn: Es ist eigentlich auch gut, vielleicht ein schlechtes Beispiel. Die Zeit könnte besser sein - Überladen von +, - wäre sinnvoller als time.add (anotherTime).
Michael K

4
@Michael: Verknüpfte Listen std::listüberladen nicht operator[](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 der ListSchnittstelle arbeiten, die darauf abzielt, die Komplexität vollständig wegzuziehen.
Fredoverflow

5
@Thor: "Aber um sicherzugehen, müssen Sie überprüfen :)" ... Auch dies ist nicht an eine Überlastung des Operators gebunden . Wenn Sie sehen time.add(anotherTime), müssen Sie auch überprüfen, ob der Bibliotheks-Programmierer die Add-Operation "korrekt" implementiert hat (was auch immer das bedeutet).
Fredoverflow

3

Ein Unterschied zwischen Überlastung a * bund Aufrufen multiply(a,b)besteht darin, dass letzteres leicht erkannt werden kann. Wenn die multiplyFunktion für verschiedene Typen nicht überladen ist, können Sie genau herausfinden, was die Funktion tun wird, ohne die Typen von aund nachverfolgen zu müssen b.

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.


Ist der Linux-Kernel nicht in reinem C entwickelt? Warum in diesem Zusammenhang überhaupt über (Operator-) Überladung diskutieren?
Fredoverflow

Die Bedenken sind für jedes Projekt mit einem ähnlichen Entwicklungsprozess gleich, unabhängig von der Sprache. Übermäßige Überladung kann es schwierig machen, die Auswirkungen von Änderungen zu verstehen, wenn Sie nur ein paar Zeilen aus einer Patch-Datei entfernen müssen.
Scott Wales

@FredOverflow: Der Linux-Kernel ist in GCC C wirklich. Es werden alle Arten von Erweiterungen verwendet, die dem C zuweilen fast das C ++ - Feeling verleihen. Ich denke an einige der ausgefallenen Manipulationen.
Zan Lynx

2
@Scott: Es macht keinen Sinn, das "Böse" des Überladens in Bezug auf in C programmierte Projekte zu diskutieren, da C nicht die Fähigkeit besitzt, Funktionen zu überladen.
Fredoverflow

3
Linus Torvalds scheint mir einen engen Standpunkt zu haben. Er kritisiert manchmal Dinge, die für die Linux-Kernel-Programmierung nicht wirklich nützlich sind, als ob sie für den allgemeinen Gebrauch ungeeignet wären. Subversion ist ein Beispiel. Es ist ein nettes VCS, aber die Linux-Kernel-Entwicklung braucht wirklich ein verteiltes VCS, also kritisierte Linus SVN im Allgemeinen.
David Thornley

2

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.


Überladene Operatoren sollten niemals "etwas Seltsames" tun. Es ist natürlich in Ordnung, wenn es etwas Komplexes macht. Aber nur überladen, wenn es eine, offensichtliche Bedeutung hat.
Sjoerd

2

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 deleteoder gar die Einzahl *. Sie haben bestimmte Erwartungen an die Betreiber, die sich niemals ändern sollten.


+1 Machen Sie [] nicht zum Äquivalent von ++.
Michael K

3
Wollen Sie damit sagen , wir sollten nicht in der Lage sein , die Betreiber zu überlasten Sie erwähnt überhaupt ? Oder sagen Sie nur, dass wir sie nur für vernünftige Zwecke überladen sollten? Weil ich Container ohne operator[], Funktoren ohne operator(), intelligente Zeiger ohne operator->und so weiter nicht gerne sehen würde .
Fredoverflow

Ich sage, dass das potenzielle Problem der Überladung von Operatoren mit mathematischen Operationen im Vergleich zu diesen Operatoren gering ist. Mit mathematischen Operatoren etwas Kluges oder Verrücktes anzustellen, mag lästig sein, aber die aufgelisteten Operatoren, die die Leute normalerweise nicht als Operatoren, sondern als grundlegende Sprachelemente betrachten, sollten immer die von der Sprache definierten Erwartungen erfüllen. []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.
Allon Guralnek

2

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 das a =:= b =:= cwirklich 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.


1
Das Überladen bestehender Operatoren und das Erfinden neuer Operatoren ist nicht dasselbe, sondern +1 von mir.
Fredoverflow

1

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:

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;
    }
};

Inakzeptabel:

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
Mitarbeiter vermehren? Sicher ist das eine Sackgasse, wenn sie es auf dem Sitzungssaaltisch machen, das heißt.
gbjbaanb

1

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 << 1soll 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äre o.leftShift(1), würde kein vernünftiger Entwickler schreiben , es wäre so etwas wie o.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.


1

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.


oder in C # für die Verwendung von + =, um ein Ereignis an einen Delegaten zu binden. Ich denke nicht, dass es ein konstruktiver Weg ist, Sprachfunktionen für die Dummheit der Programmierer verantwortlich zu machen.
gbjbaanb

0

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 wie foo.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 thisund 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.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.