SO VIEL ICH WEISS:
C ++ bietet drei verschiedene Arten von Polymorphismus.
- Virtuelle Funktionen
- Überladung des Funktionsnamens
- Überlastung des Bedieners
Zusätzlich zu den drei oben genannten Arten von Polymorphismus gibt es andere Arten von Polymorphismus:
- Laufzeit
- Kompilierungszeit
- Ad-hoc-Polymorphismus
- parametrischer Polymorphismus
Ich weiß, dass Laufzeitpolymorphismus durch virtuelle Funktionen und statischer Polymorphismus durch Vorlagenfunktionen erreicht werden kann
Aber für die anderen beiden
Ad-hoc-Polymorphismus:
Wenn der Bereich der tatsächlichen Typen, die verwendet werden können, begrenzt ist und die Kombinationen vor der Verwendung individuell angegeben werden müssen, wird dies als Ad-hoc-Polymorphismus bezeichnet.
parametrischer Polymorphismus:
Wenn der gesamte Code ohne Erwähnung eines bestimmten Typs geschrieben wurde und somit transparent mit einer beliebigen Anzahl neuer Typen verwendet werden kann, spricht man von parametrischem Polymorphismus.
Ich kann sie kaum verstehen :(
Kann jemand sie beide wenn möglich anhand eines Beispiels erklären? Ich hoffe, die Antworten auf diese Fragen wären hilfreich für viele neue Passagen ihrer Colleges.
quelle
Antworten:
Verständnis / Anforderungen für Polymorphismus
Um den Polymorphismus zu verstehen - wie der Begriff in der Informatik verwendet wird -, ist es hilfreich, von einem einfachen Test und seiner Definition auszugehen. Erwägen:
Hier
f()
soll eine Operation ausgeführt werden und es werden Wertex
undy
als Eingaben gegeben.C ++ - Mechanismen für Polymorphismus
Expliziter vom Programmierer spezifizierter Polymorphismus
Sie können so schreiben
f()
, dass es auf verschiedene Arten mit mehreren Typen arbeiten kann:Vorverarbeitung:
Überlastung:
Vorlagen:
Virtueller Versand:
Andere verwandte Mechanismen
Vom Compiler bereitgestellter Polymorphismus für eingebaute Typen, Standardkonvertierungen und Gießen / Zwang werden der Vollständigkeit halber später erläutert als:
Terminologie
Weitere Kategorisierung
Angesichts der oben genannten polymorphen Mechanismen können wir sie auf verschiedene Arten kategorisieren:
Wann wird der polymorphe typspezifische Code ausgewählt?
f
oben nur mitint
Argumenten aufgerufen wurde - abhängig vom verwendeten polymorphen Mechanismus und den Inlining-Optionen kann der Compiler möglicherweise vermeiden, Code für zuf(double)
generieren, oder der generierte Code wird möglicherweise irgendwann beim Kompilieren oder Verknüpfen weggeworfen. ( alle oben genannten Mechanismen außer virtuellem Versand )Welche Typen werden unterstützt?
Parametrische Bedeutung Sie können einfach versuchen, die Funktion für verschiedene Parametertypen zu verwenden, ohne speziell etwas zu tun, um deren Unterstützung zu aktivieren (z. B. Vorlagen, Makros). Ein Objekt mit Funktionen / Operatoren, die sich wie die Vorlage / das Makro verhalten, erwartet 1, ist alles, was die Vorlage / das Makro für ihre Arbeit benötigt, wobei der genaue Typ irrelevant ist. Die von C ++ 20 eingeführten "Konzepte" drücken solche Erwartungen aus und setzen sie durch - siehe cppreference- Seite hier .
Parametrischer Polymorphismus ermöglicht das Tippen von Enten - ein Konzept, das James Whitcomb Riley zugeschrieben wird, der anscheinend sagte: "Wenn ich einen Vogel sehe, der wie eine Ente läuft und wie eine Ente schwimmt und wie eine Ente quakt, nenne ich diesen Vogel eine Ente." .
Der Polymorphismus des Subtyps (auch bekannt als Inklusion) ermöglicht es Ihnen, an neuen Typen zu arbeiten, ohne den Algorithmus / die Funktion zu aktualisieren. Sie müssen jedoch von derselben Basisklasse abgeleitet sein (virtueller Versand).
1 - Vorlagen sind äußerst flexibel. SFINAE (siehe auch
std::enable_if
) erlaubt effektiv mehrere Sätze von Erwartungen für parametrischen Polymorphismus. Sie können beispielsweise codieren, dass Sie, wenn der von Ihnen verarbeitete Datentyp ein.size()
Mitglied hat, eine Funktion verwenden, andernfalls eine andere Funktion, die nicht benötigt wird.size()
(aber vermutlich in irgendeiner Weise leidet - z. B.strlen()
wenn Sie die langsamere verwenden oder nicht drucken als nützlich eine Nachricht im Protokoll). Sie können auch Ad-hoc-Verhaltensweisen angeben, wenn die Vorlage mit bestimmten Parametern instanziiert wird, wobei einige Parameter entweder parametrisch bleiben ( teilweise Vorlagenspezialisierung ) oder nicht ( vollständige Spezialisierung )."Polymorph"
Alf Steinbach kommentiert, dass sich Polymorphic im C ++ Standard nur auf Laufzeitpolymorphismus mit virtuellem Versand bezieht. General Comp. Sci. Die Bedeutung ist gemäß dem Glossar des C ++ - Erstellers Bjarne Stroustrup ( http://www.stroustrup.com/glossary.html ) umfassender:
Diese Antwort bezieht - wie die Frage - C ++ - Funktionen auf die Comp. Sci. Terminologie.
Diskussion
Mit dem C ++ Standard unter Verwendung einer engeren Definition von "Polymorphismus" als mit dem Comp. Sci. Gemeinschaft, um gegenseitiges Verständnis für Ihr Publikum zu gewährleisten, berücksichtigen Sie ...
Entscheidend für einen großartigen C ++ - Programmierer ist jedoch, zu verstehen, was Polymorphismus wirklich für Sie bedeutet ...
Sie können einmal "algorithmischen" Code schreiben und ihn dann auf viele Datentypen anwenden
... und dann seien Sie sich sehr bewusst, wie unterschiedliche polymorphe Mechanismen Ihren tatsächlichen Bedürfnissen entsprechen.
Laufzeitpolymorphismus passt:
Base*
s,Wenn es keinen eindeutigen Treiber für den Laufzeitpolymorphismus gibt, sind Optionen zur Kompilierungszeit häufig vorzuziehen. Erwägen:
__FILE__
,__LINE__
, Stringliteral Verkettung und andere Fähigkeiten von Makros (die bleiben böse ;-))Andere Mechanismen, die den Polymorphismus unterstützen
Wie versprochen werden der Vollständigkeit halber mehrere periphere Themen behandelt:
Diese Antwort schließt mit einer Diskussion darüber, wie das oben Genannte kombiniert wird, um polymorphen Code zu stärken und zu vereinfachen - insbesondere parametrischen Polymorphismus (Vorlagen und Makros).
Mechanismen zur Zuordnung zu typspezifischen Operationen
> Implizite vom Compiler bereitgestellte Überladungen
Konzeptionell überlastet der Compiler viele Operatoren für integrierte Typen. Es unterscheidet sich konzeptionell nicht von benutzerdefinierter Überladung, wird jedoch aufgelistet, da es leicht übersehen wird. Beispielsweise können Sie
int
s unddouble
s mit derselben Notation hinzufügen,x += 2
und der Compiler erzeugt:Das Überladen erstreckt sich dann nahtlos auf benutzerdefinierte Typen:
Vom Compiler bereitgestellte Überladungen für Basistypen sind in Hochsprachen (3GL +) häufig anzutreffen, und die explizite Diskussion des Polymorphismus impliziert im Allgemeinen etwas mehr. (Bei 2GLs - Assemblersprachen - muss der Programmierer häufig explizit unterschiedliche Mnemoniken für unterschiedliche Typen verwenden.)
> Standardkonvertierungen
Der vierte Abschnitt des C ++ - Standards beschreibt Standardkonvertierungen.
Der erste Punkt fasst gut zusammen (aus einem alten Entwurf - hoffentlich immer noch im Wesentlichen korrekt):
Keine oder eine Konvertierung aus dem folgenden Satz: Konvertierung von Wert zu Wert, Konvertierung von Array zu Zeiger und Konvertierung von Funktion zu Zeiger.
Null oder eine Konvertierung aus dem folgenden Satz: Integral-Promotions, Gleitkomma-Promotion, Integral-Konvertierungen, Gleitkomma-Konvertierungen, Gleitkomma-Integral-Konvertierungen, Zeiger-Konvertierungen, Zeiger-zu-Mitglied-Konvertierungen und Boolesche Konvertierungen.
Keine oder eine Qualifikationsumwandlung.
Diese Konvertierungen ermöglichen Code wie:
Anwenden des früheren Tests:
a()
selbst führt Code speziell für ausdouble
und ist daher nicht polymorph.Beim zweiten Aufruf
a()
des Compilers muss jedoch ein typgerechter Code für eine "Gleitkomma-Heraufstufung" (Standard §4) generiert werden , in die konvertiert werden42
soll42.0
. Dieser zusätzliche Code befindet sich in der aufrufenden Funktion. Wir werden die Bedeutung davon in der Schlussfolgerung diskutieren.> Zwang, Casts, implizite Konstruktoren
Mit diesen Mechanismen können benutzerdefinierte Klassen Verhaltensweisen angeben, die den Standardkonvertierungen der integrierten Typen ähneln. Werfen wir einen Blick:
Hier wird das Objekt
std::cin
mit Hilfe eines Konvertierungsoperators in einem booleschen Kontext ausgewertet. Dies kann konzeptionell mit "Integral Promotions" et al. Aus den Standard-Conversions im obigen Thema gruppiert werden.Implizite Konstruktoren machen effektiv dasselbe, werden jedoch vom Cast-to-Typ gesteuert:
Auswirkungen von vom Compiler bereitgestellten Überladungen, Konvertierungen und Zwang
Erwägen:
Wenn wir möchten, dass der Betrag
x
während der Division als reelle Zahl behandelt wird (dh 6,5 anstatt auf 6 abgerundet), müssen wir nur auf änderntypedef double Amount
.Das ist schön, aber es wäre nicht zu viel Arbeit gewesen, den Code explizit "richtig eingeben" zu lassen:
Bedenken Sie jedoch, dass wir die erste Version in eine
template
:Aufgrund dieser kleinen "Komfortfunktionen" kann es so einfach instanziiert werden, dass es entweder funktioniert
int
oderdouble
wie beabsichtigt funktioniert. Ohne diese Funktionen benötigen wir explizite Casts, Typmerkmale und / oder Richtlinienklassen, einige ausführliche, fehleranfällige Fehler wie:Vom Compiler bereitgestellte Operatorüberladung für integrierte Typen, Standardkonvertierungen, Casting / Zwang / implizite Konstruktoren - alle tragen subtil zur Unterstützung des Polymorphismus bei. Aus der Definition oben in dieser Antwort geht hervor, dass "typgerechten Code gefunden und ausgeführt wird", indem Folgendes zugeordnet wird:
"weg" von Parametertypen
aus den vielen Datentypen polymorphe algorithmische Code-Handles
In dem Code für eine (möglicherweise geringere) Anzahl der (gleicher oder anderer) Arten geschrieben.
Parametertypen "bis" aus Werten des konstanten Typs
Sie erstellen keine polymorphen Kontexte selbst, sondern helfen dabei, Code in solchen Kontexten zu stärken / zu vereinfachen.
Sie fühlen sich vielleicht betrogen ... es scheint nicht viel zu sein. Die Bedeutung besteht darin, dass wir in parametrischen polymorphen Kontexten (dh innerhalb von Vorlagen oder Makros) versuchen, einen beliebig großen Bereich von Typen zu unterstützen, aber häufig Operationen an ihnen in Form anderer Funktionen, Literale und Operationen ausdrücken möchten, die für a entwickelt wurden kleiner Satz von Typen. Es reduziert die Notwendigkeit, nahezu identische Funktionen oder Daten pro Typ zu erstellen, wenn die Operation / der Wert logisch identisch ist. Diese Funktionen wirken zusammen, um eine Haltung der "besten Anstrengung" hinzuzufügen, indem sie das tun, was intuitiv erwartet wird, indem sie die begrenzten verfügbaren Funktionen und Daten verwenden und nur dann mit einem Fehler aufhören, wenn echte Unklarheiten bestehen.
Dies hilft dabei, den Bedarf an polymorphem Code zu begrenzen, der polymorphen Code unterstützt, ein engeres Netz um die Verwendung von Polymorphismus zu ziehen, damit die lokalisierte Verwendung keine weit verbreitete Verwendung erzwingt, und die Vorteile des Polymorphismus nach Bedarf verfügbar zu machen, ohne die Kosten für die Offenlegung bei zu verursachen Kompilierungszeit, mehrere Kopien derselben logischen Funktion im Objektcode zur Unterstützung der verwendeten Typen und beim virtuellen Versand im Gegensatz zu Inlining- oder zumindest zur Kompilierungszeit aufgelösten Aufrufen. Wie in C ++ üblich, hat der Programmierer viel Freiheit, die Grenzen zu steuern, innerhalb derer Polymorphismus verwendet wird.
quelle
dynamic_cast
erfordert §5.2.7 / 6 about einen "Zeiger auf oder einen Wert eines polymorphen Typs". Prost & hth.,In C ++ ist die wichtige Unterscheidung die Laufzeit- und die Kompilierungsbindung. Ad-hoc vs. parametrisch hilft nicht wirklich, wie ich später erklären werde.
Hinweis - Der Laufzeitpolymorphismus wird möglicherweise zur Kompilierungszeit noch aufgelöst, dies ist jedoch nur eine Optimierung. Die Notwendigkeit, die Laufzeitauflösung effizient zu unterstützen und gegen andere Probleme abzuwägen, ist Teil dessen, was dazu geführt hat, dass virtuelle Funktionen so sind, wie sie sind. Und das ist wirklich der Schlüssel für alle Formen des Polymorphismus in C ++ - jede ergibt sich aus verschiedenen Kompromissen, die in einem anderen Kontext gemacht wurden.
Funktionsüberladung und Bedienerüberladung sind in jeder Hinsicht dasselbe. Die Namen und die Syntax für ihre Verwendung haben keinen Einfluss auf den Polymorphismus.
Mit Vorlagen können Sie viele Funktionsüberladungen gleichzeitig angeben.
Es gibt eine andere Reihe von Namen für dieselbe Auflösungszeit-Idee ...
Diese Namen sind eher mit OOP verbunden, daher ist es etwas seltsam zu sagen, dass eine Vorlage oder eine andere Nichtmitgliedsfunktion eine frühe Bindung verwendet.
Um die Beziehung zwischen virtuellen Funktionen und Funktionsüberladung besser zu verstehen, ist es auch nützlich, den Unterschied zwischen "Einzelversand" und "Mehrfachversand" zu verstehen. Die Idee kann als Fortschritt verstanden werden ...
OOP ist offensichtlich mehr als eine Ausrede, einen Parameter als speziell zu nominieren, aber das ist ein Teil davon. Und zurück zu dem, was ich über Kompromisse gesagt habe: Einzelversand ist recht einfach und effizient durchzuführen (die übliche Implementierung wird als "virtuelle Tabellen" bezeichnet). Mehrfachversand ist nicht nur in Bezug auf die Effizienz, sondern auch für die separate Zusammenstellung umständlicher. Wenn Sie neugierig sind, können Sie "das Ausdrucksproblem" nachschlagen.
So wie es etwas seltsam ist, den Begriff "frühe Bindung" für Nichtmitgliedsfunktionen zu verwenden, ist es etwas seltsam, die Begriffe "Einzelversand" und "Mehrfachversand" zu verwenden, bei denen der Polymorphismus zur Kompilierungszeit aufgelöst wird. Normalerweise wird davon ausgegangen, dass C ++ keinen Mehrfachversand aufweist, was als eine bestimmte Art der Laufzeitauflösung angesehen wird. Das Überladen von Funktionen kann jedoch als Mehrfachversand zur Kompilierungszeit angesehen werden.
Zurück zum parametrischen vs. Ad-hoc-Polymorphismus: Diese Begriffe sind in der funktionalen Programmierung beliebter und funktionieren in C ++ nicht ganz. Sogar so...
Parametrischer Polymorphismus bedeutet, dass Sie Typen als Parameter haben und genau derselbe Code verwendet wird, unabhängig davon, welchen Typ Sie für diese Parameter verwenden.
Ad-hoc-Polymorphismus ist Ad-hoc in dem Sinne, dass Sie je nach Typ unterschiedlichen Code bereitstellen.
Überladung und virtuelle Funktionen sind Beispiele für Ad-hoc-Polymorphismus.
Wieder gibt es einige Synonyme ...
Abgesehen davon, dass dies keine Synonyme sind, obwohl sie häufig so behandelt werden, als ob sie es wären, und dass hier in C ++ wahrscheinlich Verwirrung entsteht.
Der Grund dafür, diese als Synonyme zu behandeln, besteht darin, dass durch die Beschränkung des Polymorphismus auf bestimmte Klassen von Typen die Verwendung von Operationen möglich wird, die für diese Klassen von Typen spezifisch sind. Das Wort "Klassen" kann hier im OOP-Sinne interpretiert werden, bezieht sich jedoch nur auf (normalerweise benannte) Sätze von Typen, die bestimmte Operationen gemeinsam haben.
Daher wird parametrischer Polymorphismus normalerweise (zumindest standardmäßig) verwendet, um einen uneingeschränkten Polymorphismus zu implizieren. Da unabhängig von den Typparametern derselbe Code verwendet wird, können nur Operationen unterstützt werden, die für alle Typen funktionieren. Indem Sie die Anzahl der Typen nicht einschränken, schränken Sie die Anzahl der Operationen, die Sie auf diese Typen anwenden können, stark ein.
In zB Haskell können Sie haben ...
Das
a
hier ist ein uneingeschränkter polymorpher Typ. Es könnte alles sein, also können wir mit Werten dieses Typs nicht viel anfangen.Hier
a
ist er darauf beschränkt, Mitglied derNum
Klassentypen zu sein, die sich wie Zahlen verhalten. Mit dieser Einschränkung können Sie mit diesen Werten nummerierte Dinge tun, z. B. sie hinzufügen. Sogar die3
Inferenz3
vom Typ Polymorph stellt fest, dass Sie den Typ meinena
.Ich betrachte dies als eingeschränkten parametrischen Polymorphismus. Es gibt nur eine Implementierung, die jedoch nur in eingeschränkten Fällen angewendet werden kann. Der Ad-hoc-Aspekt ist die Auswahl
+
und3
Verwendung. Jede "Instanz" vonNum
hat ihre eigene Implementierung. Selbst in Haskell sind "parametrisch" und "ungezwungen" keine Synonyme - beschuldigen Sie mich nicht, es ist nicht meine Schuld!In C ++ sind sowohl Überladung als auch virtuelle Funktionen Ad-hoc-Polymorphismus. Die Definition des Ad-hoc-Polymorphismus spielt keine Rolle, ob die Implementierung zur Laufzeit oder zur Kompilierungszeit ausgewählt wird.
C ++ kommt dem parametrischen Polymorphismus mit Vorlagen sehr nahe, wenn jeder Vorlagenparameter einen Typ hat
typename
. Es gibt Typparameter und eine einzige Implementierung, unabhängig davon, welche Typen verwendet werden. Die Regel "Substitutionsfehler ist kein Fehler" bedeutet jedoch, dass implizite Einschränkungen durch die Verwendung von Operationen in der Vorlage entstehen. Zusätzliche Komplikationen sind die Vorlagenspezialisierung für die Bereitstellung alternativer Vorlagen - verschiedene (Ad-hoc-) Implementierungen.In gewisser Weise hat C ++ einen parametrischen Polymorphismus, der jedoch implizit eingeschränkt ist und durch Ad-hoc-Alternativen überschrieben werden kann - dh diese Klassifizierung funktioniert für C ++ nicht wirklich.
quelle
a
Hier ist ein [...] uneingeschränkter polymorpher Typ, daher können wir mit Werten dieses Typs nicht viel anfangen." war von Interesse - in C ++ sans Concepts sind Sie nicht darauf beschränkt, nur einen bestimmten Satz von Operationen mit einem Argument eines als Vorlagenparameter angegebenen Typs zu versuchen ... Bibliotheken wie Boost-Konzepte funktionieren umgekehrt - stellen Sie sicher, dass der Typ Operationen unterstützt Sie geben an, anstatt sich vor versehentlichem Einsatz zusätzlicher Vorgänge zu schützen.Ad-hoc-Polymorphismus bedeutet Funktionsüberladung oder Operatorüberladung. Hier geht's:
http://en.wikipedia.org/wiki/Ad-hoc_polymorphism
In Bezug auf den parametrischen Polymorphismus können Vorlagenfunktionen auch berücksichtigt werden, da sie nicht unbedingt Parameter von FIXED-Typen berücksichtigen. Beispielsweise kann eine Funktion ein Array von Ganzzahlen sortieren und sie kann auch ein Array von Zeichenfolgen usw. sortieren.
http://en.wikipedia.org/wiki/Parametric_polymorphism
quelle
<
und ähnliche Operatoren bereitstellen ). In Haskell würden Sie diese Anforderung explizit mit der Klasse ausdrückenOrd
. Die Tatsache, dass Sie<
je nach Typ (wie von der Instanz von angegebenOrd
) unterschiedliche Werte erhalten, wird als Ad-hoc-Polymorphismus betrachtet.Dies mag nicht hilfreich sein, aber ich habe dies gemacht, um meine Freunde in die Programmierung einzuführen, indem ich definierte Funktionen wie
START
undEND
für die Hauptfunktion herausgab, damit es nicht zu entmutigend war (sie verwendeten nur die Datei main.cpp ). Es enthält polymorphe Klassen und Strukturen, Vorlagen, Vektoren, Arrays, Präprozessor-Direktiven, Freundschaften, Operatoren und Zeiger (die Sie wahrscheinlich alle kennen sollten, bevor Sie Polymorphismus versuchen):Hinweis: Es ist noch nicht fertig, aber Sie können sich ein Bild machen
main.cpp
main.h
quelle
Hier ist ein grundlegendes Beispiel für die Verwendung polymorpher Klassen
quelle
Polymorphismus bedeutet viele Formen als solche, die ein Operator verwendet, um unter verschiedenen Instanzen unterschiedlich zu handeln. Polymorphismus wird verwendet, um Vererbung zu implementieren. Zum Beispiel haben wir ein fn draw () für eine Klassenform definiert, dann kann das Zeichnen fn zum Zeichnen von Kreisen, Kästchen, Dreiecken und anderen Formen implementiert werden. (die Objekte der Klassenform sind)
quelle
Wenn jemand diesen Leuten CUT sagt
Was wird passieren?
Die obige Darstellung zeigt also, was Polymorphismus (gleicher Name, unterschiedliches Verhalten) in OOP ist.
Wenn Sie zu einem Interview gehen und der Interviewer Sie bittet, ein Live-Beispiel für Polymorphismus in demselben Raum zu erzählen / zu zeigen, in dem wir sitzen, sagen wir:
Antwort - Tür / Fenster
Fragen Sie sich, wie?
Durch Tür / Fenster - eine Person kann kommen, Luft kann kommen, Licht kann kommen, Regen kann kommen usw.
dh man bildet ein anderes Verhalten (Polymorphismus).
Um es besser und auf einfache Weise zu verstehen, habe ich das obige Beispiel verwendet. Wenn Sie eine Referenz für den Code benötigen, folgen Sie den obigen Antworten.
quelle