Ich lerne C ++ und steige gerade in virtuelle Funktionen ein.
Nach dem, was ich gelesen habe (im Buch und online), sind virtuelle Funktionen Funktionen in der Basisklasse, die Sie in abgeleiteten Klassen überschreiben können.
Aber früher in diesem Buch konnte ich beim Erlernen der grundlegenden Vererbung Basisfunktionen in abgeleiteten Klassen überschreiben, ohne sie zu verwenden virtual
.
Was vermisse ich hier? Ich weiß, dass virtuelle Funktionen mehr beinhalten, und es scheint wichtig zu sein, deshalb möchte ich klarstellen, was es genau ist. Ich kann online einfach keine klare Antwort finden.
c++
virtual-functions
Jake Wilson
quelle
quelle
Antworten:
So habe ich nicht nur verstanden, was
virtual
Funktionen sind, sondern warum sie benötigt werden:Angenommen, Sie haben diese beiden Klassen:
In Ihrer Hauptfunktion:
So weit so gut, oder? Tiere fressen generisches Futter, Katzen fressen Ratten, alles ohne
virtual
.Lassen Sie es uns jetzt ein wenig ändern, damit es
eat()
über eine Zwischenfunktion aufgerufen wird (eine triviale Funktion nur für dieses Beispiel):Jetzt ist unsere Hauptfunktion:
Oh oh ... wir haben eine Katze reingelegt
func()
, aber sie frisst keine Ratten. Sollten Sie überladen,func()
damit es eine dauertCat*
? Wenn Sie mehr Tiere von Animal ableiten müssten, würden sie alle ihre eigenen brauchenfunc()
.Die Lösung besteht darin,
eat()
aus derAnimal
Klasse eine virtuelle Funktion zu machen:Main:
Erledigt.
quelle
virtual
, es wird eine dynamische Bindung gegen statische eingeführt, und ja, es ist seltsam, wenn Sie aus Sprachen wie Java kommen.Ohne "virtuell" erhalten Sie "frühzeitige Bindung". Welche Implementierung der Methode verwendet wird, wird zur Kompilierungszeit basierend auf dem Typ des Zeigers, den Sie aufrufen, entschieden.
Mit "virtuell" erhalten Sie "späte Bindung". Welche Implementierung der Methode verwendet wird, wird zur Laufzeit anhand des Typs des Objekts festgelegt, auf das verwiesen wird - wie es ursprünglich konstruiert wurde. Dies ist nicht unbedingt das, was Sie basierend auf dem Typ des Zeigers denken würden, der auf dieses Objekt zeigt.
BEARBEITEN - siehe diese Frage .
Außerdem behandelt dieses Tutorial die frühe und späte Bindung in C ++.
quelle
main
Funktion usw. aufgerufen. Zeiger-zu-abgeleitet wandelt implizit in Zeiger-zu-Basis um (spezialisierter impliziert implizit allgemeinere). Umgekehrt benötigen Sie eine explizite Besetzung, normalerweise adynamic_cast
. Alles andere - sehr anfällig für undefiniertes Verhalten. Stellen Sie also sicher, dass Sie wissen, was Sie tun. Nach meinem besten Wissen hat sich dies seit C ++ 98 nicht geändert.Sie benötigen mindestens 1 Vererbungsstufe und einen Downcast, um dies zu demonstrieren. Hier ist ein sehr einfaches Beispiel:
quelle
Sie benötigen virtuelle Methoden für sicheres Downcasting , Einfachheit und Prägnanz .
Das ist, was virtuelle Methoden tun: Sie werden sicher mit scheinbar einfachem und präzisem Code heruntergestuft, um die unsicheren manuellen Umwandlungen in dem komplexeren und ausführlicheren Code zu vermeiden, den Sie sonst hätten.
Nicht virtuelle Methode ⇒ statische Bindung
Der folgende Code ist absichtlich "falsch". Es deklariert die
value
Methode nicht alsvirtual
und erzeugt daher ein unbeabsichtigtes "falsches" Ergebnis, nämlich 0:In der als "schlecht" kommentierten Zeile wird die
Expression::value
Methode aufgerufen, da der statisch bekannte Typ (der zur Kompilierungszeit bekannte Typ ) istExpression
und dievalue
Methode nicht virtuell ist.Virtuelle Methode ⇒ dynamische Bindung.
Durch Deklarieren
value
wievirtual
beim statisch bekannten Typ wirdExpression
sichergestellt, dass bei jedem Aufruf überprüft wird, um welchen tatsächlichen Objekttyp es sich handelt, und die entsprechende Implementierungvalue
für diesen dynamischen Typ aufgerufen wird :Hier ist die Ausgabe so,
6.86
wie sie sein sollte, da die virtuelle Methode virtuell aufgerufen wird . Dies wird auch als dynamische Bindung der Anrufe bezeichnet. Es wird eine kleine Überprüfung durchgeführt, bei der der tatsächliche dynamische Objekttyp ermittelt und die entsprechende Methodenimplementierung für diesen dynamischen Typ aufgerufen wird.Die relevante Implementierung ist die in der spezifischsten (am meisten abgeleiteten) Klasse.
Beachten Sie, dass Methodenimplementierungen in abgeleiteten Klassen hier nicht markiert sind
virtual
, sondern stattdessenoverride
. Sie könnten markiert werdenvirtual
, sind aber automatisch virtuell. Dasoverride
Schlüsselwort stellt sicher, dass, wenn es in einer Basisklasse keine solche virtuelle Methode gibt , eine Fehlermeldung angezeigt wird (was wünschenswert ist).Die Hässlichkeit, dies ohne virtuelle Methoden zu tun
Ohne
virtual
müsste man eine Do It Yourself- Version der dynamischen Bindung implementieren . Dies beinhaltet im Allgemeinen unsicheres manuelles Downcasting, Komplexität und Ausführlichkeit.Für den Fall einer einzelnen Funktion, wie hier, reicht es aus, einen Funktionszeiger im Objekt zu speichern und über diesen Funktionszeiger aufzurufen, aber dennoch sind einige unsichere Downcasts, Komplexität und Ausführlichkeit erforderlich:
Eine positive Sichtweise ist, dass, wenn Sie wie oben auf unsicheres Downcasting, Komplexität und Ausführlichkeit stoßen, oft eine virtuelle Methode oder virtuelle Methoden wirklich helfen können.
quelle
Virtuelle Funktionen werden zur Unterstützung des Laufzeitpolymorphismus verwendet .
Das heißt, virtuelle sagt Schlüsselwort der Compiler nicht die Entscheidung zu treffen (der Funktion Bindung) bei der Kompilierung, sondern verschieben es für Runtime“ .
Sie können eine Funktion virtuell machen, indem Sie dem Schlüsselwort
virtual
in seiner Basisklassendeklaration vorangehen . Zum Beispiel,Wenn eine Basisklasse eine virtuelle Memberfunktion hat, kann jede Klasse, die erbt von der Basisklasse neu definiert die Funktion mit genau den gleichen Prototyp dh nur Funktionalität neu definiert werden, nicht die Schnittstelle der Funktion.
Ein Basisklassenzeiger kann verwendet werden, um sowohl auf ein Basisklassenobjekt als auch auf ein abgeleitetes Klassenobjekt zu verweisen.
quelle
Wenn die Basisklasse
Base
und eine abgeleitete Klasse istDer
, können Sie einenBase *p
Zeiger haben, der tatsächlich auf eine Instanz von zeigtDer
. Wenn Sie aufrufenp->foo();
, wennfoo
es nicht virtuell ist, wirdBase
die Version davon ausgeführt, wobei die Tatsache ignoriert wird, diep
tatsächlich auf a verweistDer
. Wenn foo ist virtuell,p->foo()
führt die „leafmost“ außer Kraft gesetztfoo
, vollständig unter Berücksichtigung der tatsächlichen Klasse des spitzen-Punkt. So ist der Unterschied zwischen virtuellen und nicht virtuellen ist eigentlich ziemlich entscheidend: die frühere Laufzeit ermöglicht Polymorphismus , das Kernkonzept der OO - Programmierung, während letzteres nicht der Fall ist.quelle
Notwendigkeit einer virtuellen Funktion erklärt [Leicht zu verstehen]
Ausgabe wird sein:
Aber mit virtueller Funktion:
Ausgabe wird sein:
Daher können Sie mit der virtuellen Funktion einen Laufzeitpolymorphismus erzielen.
quelle
Ich möchte eine weitere Verwendung der virtuellen Funktion hinzufügen, obwohl sie das gleiche Konzept wie die oben angegebenen Antworten verwendet, aber ich denke, es ist erwähnenswert.
VIRTUELLER ZERSTÖRER
Betrachten Sie dieses Programm unten, ohne den Destruktor der Basisklasse als virtuell zu deklarieren. Der Speicher für Cat wird möglicherweise nicht bereinigt.
Ausgabe:
Ausgabe:
quelle
without declaring Base class destructor as virtual; memory for Cat may not be cleaned up.
Es ist schlimmer als das. Das Löschen eines abgeleiteten Objekts über einen Basiszeiger / eine Referenz ist ein reines undefiniertes Verhalten. Es ist also nicht nur so, dass Speicherplatz verloren geht. Vielmehr ist das Programm schlecht geformt, so dass der Compiler es in alles umwandeln kann: Maschinencode, der zufällig gut funktioniert oder nichts tut, oder Dämonen aus der Nase beschwört oder so weiter. Deshalb, wenn ein Programm so entworfen ist Damit ein Benutzer eine abgeleitete Instanz über eine Basisreferenz löschen kann, muss die Basis einen virtuellen Destruktor habenSie müssen zwischen Überschreiben und Überladen unterscheiden. Ohne das
virtual
Schlüsselwort überladen Sie nur eine Methode einer Basisklasse. Das bedeutet nichts als sich zu verstecken. Angenommen, Sie haben eine BasisklasseBase
und eine abgeleitete Klasse,Specialized
die beide implementierenvoid foo()
. Jetzt haben Sie einen ZeigerBase
auf eine Instanz vonSpecialized
. Wenn Sie es aufrufen,foo()
können Sie den Unterschied beobachten, der Folgendesvirtual
ausmacht: Wenn die Methode virtuell ist, wird die Implementierung vonSpecialized
verwendet. Wenn sie fehlt, wird die Version vonBase
ausgewählt. Es wird empfohlen, Methoden aus einer Basisklasse niemals zu überladen. Wenn eine Methode nicht virtuell ist, kann der Autor Ihnen mitteilen, dass ihre Erweiterung in Unterklassen nicht beabsichtigt ist.quelle
virtual
Sie nicht überladen. Du beschattest . Wenn eine BasisklasseB
hat eine oder mehr Funktionenfoo
, und die abgeleitete KlasseD
definiert einenfoo
Namen, dasfoo
Häuten aller diesefoo
es inB
. Sie werdenB::foo
mit Scope Resolution erreicht. UmB::foo
FunktionenD
zum Überladen zu fördern , müssen Sie verwendenusing B::foo
.Schnelle Antwort:
In Bjarne Stroustrup C ++ - Programmierung: Prinzipien und Praxis, (14.3):
1. Die Verwendung von Vererbung, Laufzeitpolymorphismus und Kapselung ist die häufigste Definition der objektorientierten Programmierung .
2. Sie können die Funktionalität nicht so codieren, dass sie schneller ist oder weniger Speicher verwendet, indem Sie andere Sprachfunktionen verwenden, um zur Laufzeit zwischen Alternativen auszuwählen. Bjarne Stroustrup C ++ Programmierung: Prinzipien und Praxis (14.3.1) .
3. Etwas zu sagen, welche Funktion wirklich aufgerufen wird, wenn wir die Basisklasse aufrufen, die die virtuelle Funktion enthält.
quelle
Ich habe meine Antwort in Form eines Gesprächs, um sie besser lesen zu können:
Warum brauchen wir virtuelle Funktionen?
Wegen des Polymorphismus.
Was ist Polymorphismus?
Die Tatsache, dass ein Basiszeiger auch auf abgeleitete Typobjekte verweisen kann.
Wie führt diese Definition des Polymorphismus zur Notwendigkeit virtueller Funktionen?
Nun, durch frühes Binden .
Was ist frühe Bindung?
Frühe Bindung (Bindung zur Kompilierungszeit) in C ++ bedeutet, dass ein Funktionsaufruf behoben wird, bevor das Programm ausgeführt wird.
Damit...?
Wenn Sie also einen Basistyp als Parameter einer Funktion verwenden, erkennt der Compiler nur die Basisschnittstelle. Wenn Sie diese Funktion mit Argumenten aus abgeleiteten Klassen aufrufen, wird sie abgeschnitten, was nicht der Fall ist.
Wenn es nicht das ist, was wir wollen, warum ist das erlaubt?
Weil wir Polymorphismus brauchen!
Was ist dann der Vorteil des Polymorphismus?
Sie können einen Basistypzeiger als Parameter einer einzelnen Funktion verwenden und dann zur Laufzeit Ihres Programms ohne Probleme auf jede der abgeleiteten Typschnittstellen (z. B. deren Elementfunktionen) zugreifen, indem Sie die Dereferenzierung dieser einzelnen Funktion verwenden Basiszeiger.
Ich weiß immer noch nicht, wofür virtuelle Funktionen gut sind ...! Und das war meine erste Frage!
Das liegt daran, dass Sie Ihre Frage zu früh gestellt haben!
Warum brauchen wir virtuelle Funktionen?
Angenommen, Sie haben eine Funktion mit einem Basiszeiger aufgerufen, der die Adresse eines Objekts aus einer seiner abgeleiteten Klassen hatte. Wie wir oben bereits erwähnt haben, wird dieser Zeiger zur Laufzeit dereferenziert. Bisher so gut, dass wir jedoch erwarten, dass eine Methode (== eine Mitgliedsfunktion) "von unserer abgeleiteten Klasse" ausgeführt wird! In der Basisklasse ist jedoch bereits dieselbe Methode definiert (eine mit demselben Header). Warum sollte sich Ihr Programm also die Mühe machen, die andere Methode auszuwählen? Mit anderen Worten, ich meine, wie können Sie dieses Szenario von dem unterscheiden, was wir früher normalerweise gesehen haben?
Die kurze Antwort lautet "eine virtuelle Mitgliedsfunktion in der Basis", und eine etwas längere Antwort lautet: "Wenn das Programm in diesem Schritt eine virtuelle Funktion in der Basisklasse sieht, weiß es (erkennt), dass Sie versuchen, sie zu verwenden Polymorphismus "und geht so zu abgeleiteten Klassen (unter Verwendung von v-table , einer Form der späten Bindung), um herauszufinden, dass eine andere Methode mit demselben Header, aber mit - erwartungsgemäß - einer anderen Implementierung.
Warum eine andere Implementierung?
Du Knöchelkopf! Geh und lies ein gutes Buch !
OK, warte, warte, warte, warum sollte man sich die Mühe machen, Basiszeiger zu verwenden, wenn man einfach abgeleitete Typzeiger verwenden könnte? Sie sind der Richter, sind all diese Kopfschmerzen es wert? Schauen Sie sich diese beiden Schnipsel an:
// 1:
// 2:
OK, obwohl ich denke, dass 1 immer noch besser als 2 ist , könnten Sie 1 auch so schreiben :
// 1:
Darüber hinaus sollten Sie sich darüber im Klaren sein, dass dies nur eine erfundene Verwendung all der Dinge ist, die ich Ihnen bisher erklärt habe. Nehmen Sie stattdessen beispielsweise eine Situation an, in der Sie eine Funktion in Ihrem Programm hatten, die die Methoden aus jeder der abgeleiteten Klassen verwendete (getMonthBenefit ()):
Versuchen Sie nun, dies ohne Kopfschmerzen neu zu schreiben !
Und tatsächlich könnte dies auch noch ein erfundenes Beispiel sein!
quelle
Wenn Sie eine Funktion in der Basisklasse haben, können Sie
Redefine
oderOverride
sie in der abgeleiteten Klasse.Methode neu definieren : In der abgeleiteten Klasse wird eine neue Implementierung für die Methode der Basisklasse angegeben. Erleichtert nicht
Dynamic binding
.Überschreiben einer Methode :
Redefining
avirtual method
der Basisklasse in der abgeleiteten Klasse. Die virtuelle Methode erleichtert die dynamische Bindung .Als Sie sagten:
Sie haben es nicht überschrieben, da die Methode in der Basisklasse nicht virtuell war, sondern Sie haben sie neu definiert
quelle
Es hilft, wenn Sie die zugrunde liegenden Mechanismen kennen. C ++ formalisiert einige Codierungstechniken, die von C-Programmierern verwendet werden. "Klassen" werden durch "Overlays" ersetzt. Strukturen mit gemeinsamen Header-Abschnitten werden verwendet, um Objekte unterschiedlichen Typs zu verarbeiten, jedoch mit einigen gemeinsamen Daten oder Operationen. Normalerweise hat die Basisstruktur des Overlays (der gemeinsame Teil) einen Zeiger auf eine Funktionstabelle, die auf einen anderen Satz von Routinen für jeden Objekttyp verweist. C ++ macht dasselbe, verbirgt jedoch die Mechanismen, dh das C ++,
ptr->func(...)
in dem func virtuell ist wie C(*ptr->func_table[func_num])(ptr,...)
, wobei sich der Inhalt von func_table zwischen abgeleiteten Klassen ändert. [Eine nicht virtuelle Methode ptr-> func () übersetzt nur in mangled_func (ptr, ..).]Das Ergebnis davon ist, dass Sie nur die Basisklasse verstehen müssen, um die Methoden einer abgeleiteten Klasse aufzurufen. Wenn eine Routine Klasse A versteht, können Sie ihr einen abgeleiteten Zeiger der Klasse B übergeben, dann werden die virtuellen Methoden aufgerufen von B statt A, da Sie durch die Funktionstabelle gehen, auf die B zeigt.
quelle
Das Schlüsselwort virtual teilt dem Compiler mit, dass keine frühzeitige Bindung durchgeführt werden soll. Stattdessen sollten automatisch alle Mechanismen installiert werden, die für eine späte Bindung erforderlich sind. Zu diesem Zweck erstellt der typische Compiler1 für jede Klasse, die virtuelle Funktionen enthält, eine einzelne Tabelle (VTABLE genannt). Der Compiler platziert die Adressen der virtuellen Funktionen für diese bestimmte Klasse in der VTABLE. In jeder Klasse mit virtuellen Funktionen wird heimlich ein Zeiger platziert, der als vpointer (abgekürzt als VPTR) bezeichnet wird und auf die VTABLE für dieses Objekt zeigt. Wenn Sie einen virtuellen Funktionsaufruf über einen Basisklassenzeiger ausführen, fügt der Compiler leise Code ein, um den VPTR abzurufen und die Funktionsadresse in der VTABLE nachzuschlagen. Dadurch wird die richtige Funktion aufgerufen und es kommt zu einer späten Bindung.
Weitere Details unter diesem Link http://cplusplusinterviews.blogspot.sg/2015/04/virtual-mechanism.html
quelle
Das virtuelle Schlüsselwort zwingt den Compiler, die in der Objektklasse und nicht in der Zeigerklasse definierte Methodenimplementierung auszuwählen .
Im obigen Beispiel wird Shape :: getName standardmäßig aufgerufen, es sei denn, getName () ist in der Basisklasse Shape als virtuell definiert. Dies zwingt den Compiler, nach der Implementierung von getName () in der Triangle-Klasse und nicht in der Shape-Klasse zu suchen.
Die virtuelle Tabelle ist der Mechanismus, mit dem der Compiler die verschiedenen Implementierungen der Unterklassen für virtuelle Methoden verfolgt. Dies wird auch als dynamischer Versand bezeichnet und ist mit einem gewissen Overhead verbunden.
Warum wird Virtual überhaupt in C ++ benötigt, warum nicht das Standardverhalten wie in Java?
quelle
Warum brauchen wir virtuelle Funktionen?
Virtuelle Funktionen vermeiden unnötige Typumwandlungsprobleme, und einige von uns können darüber diskutieren, warum wir virtuelle Funktionen benötigen, wenn wir den abgeleiteten Klassenzeiger verwenden können, um die in der abgeleiteten Klasse spezifische Funktion aufzurufen. Die Antwort lautet: Sie macht die gesamte Idee der Vererbung in einem großen System zunichte Entwicklung, bei der ein Einzelzeiger-Basisklassenobjekt sehr erwünscht ist.
Vergleichen wir im Folgenden zwei einfache Programme, um die Bedeutung virtueller Funktionen zu verstehen:
Programm ohne virtuelle Funktionen:
AUSGABE:
Programm mit virtueller Funktion:
AUSGABE:
Durch genaue Analyse beider Ausgänge kann man die Bedeutung virtueller Funktionen verstehen.
quelle
OOP Antwort: Subtyp Polymorphismus
In C ++ werden virtuelle Methoden benötigt, um Polymorphismus zu realisieren , genauer Subtypisierung oder Subtyppolymorphismus, wenn Sie die Definition aus Wikipedia anwenden.
Wikipedia, Subtyping, 2019-01-09: In der Programmiersprachentheorie ist Subtyping (auch Subtyp-Polymorphismus oder Einschlusspolymorphismus) eine Form des Typpolymorphismus, bei der ein Subtyp ein Datentyp ist, der durch einen Begriff mit einem anderen Datentyp (dem Supertyp) verwandt ist der Substituierbarkeit, was bedeutet, dass Programmelemente, typischerweise Unterprogramme oder Funktionen, die geschrieben wurden, um auf Elemente des Supertyps zu arbeiten, auch auf Elemente des Untertyps arbeiten können.
HINWEIS: Subtyp bedeutet Basisklasse und Subtyp bedeutet geerbte Klasse.
Weiterführende Literatur zum Subtyp Polymorphismus
Technische Antwort: Dynamischer Versand
Wenn Sie einen Zeiger auf eine Basisklasse haben, wird der Aufruf der Methode (die als virtuell deklariert ist) an die Methode der tatsächlichen Klasse des erstellten Objekts gesendet. Auf diese Weise wird der Subtyp-Polymorphismus in C ++ realisiert.
Lesen Sie weiter Polymorphismus in C ++ und Dynamic Dispatch
Implementierungsantwort: Erstellt einen vtable-Eintrag
Für jeden Modifikator "virtual" für Methoden erstellen C ++ - Compiler normalerweise einen Eintrag in der vtable der Klasse, in der die Methode deklariert ist. So realisiert der C ++ - Compiler den dynamischen Versand .
Lesen Sie weiter vtables
Beispielcode
Ausgabe des Beispielcodes
UML-Klassendiagramm des Codebeispiels
quelle
Hier ist ein vollständiges Beispiel, das zeigt, warum die virtuelle Methode verwendet wird.
quelle
In Bezug auf die Effizienz sind die virtuellen Funktionen etwas weniger effizient als die frühen Bindungsfunktionen.
"Dieser virtuelle Aufrufmechanismus kann fast so effizient gemacht werden wie der" normale Funktionsaufruf "-Mechanismus (innerhalb von 25%). Sein Speicherplatz-Overhead ist ein Zeiger in jedem Objekt einer Klasse mit virtuellen Funktionen plus einem vtbl für jede solche Klasse." [ A. Tour durch C ++ von Bjarne Stroustrup]
quelle
if(param1>param2) return cst;
dass der Compiler in einigen Fällen den gesamten Funktionsaufruf auf eine Konstante reduzieren kann).Virtuelle Methoden werden beim Schnittstellendesign verwendet. In Windows gibt es beispielsweise eine Schnittstelle namens IUnknown wie folgt:
Diese Methoden müssen vom Benutzer der Benutzeroberfläche implementiert werden. Sie sind wichtig für die Erstellung und Zerstörung bestimmter Objekte, die IUnknown erben müssen. In diesem Fall kennt die Laufzeit die drei Methoden und erwartet, dass sie beim Aufruf implementiert werden. In gewissem Sinne fungieren sie als Vertrag zwischen dem Objekt selbst und dem, was dieses Objekt verwendet.
quelle
the run-time is aware of the three methods and expects them to be implemented
Da sie rein virtuell sind, gibt es keine Möglichkeit, eine Instanz von zu erstellen. Daher müssenIUnknown
alle Unterklassen alle diese Methoden implementieren, um lediglich zu kompilieren. Es besteht keine Gefahr, sie nicht zu implementieren und dies nur zur Laufzeit herauszufinden (aber natürlich kann man sie natürlich falsch implementieren!). Und wow, heute habe ich Windows als Makro mit dem Wort gelernt , vermutlich weil ihre Benutzer nicht einfach (A) das Präfix im Namen sehen oder (B) die Klasse betrachten können, um zu sehen, dass es sich um eine Schnittstelle handelt. Ugh#define
interface
I
Ich denke, Sie beziehen sich auf die Tatsache, dass Sie, sobald eine Methode als virtuell deklariert wurde, das Schlüsselwort 'virtual' nicht mehr für Überschreibungen verwenden müssen.
Wenn Sie in der foo-Deklaration von Base nicht 'virtual' verwenden, wird das foo von Derived nur beschattet.
quelle
Hier ist eine zusammengeführte Version des C ++ - Codes für die ersten beiden Antworten.
Zwei verschiedene Ergebnisse sind:
Ohne #define virtual wird es zur Kompilierungszeit gebunden. Animal * ad und func (Animal *) verweisen alle auf die say () -Methode des Tieres.
Mit #define virtual wird es zur Laufzeit gebunden. Dog * d, Animal * ad und func (Animal *) zeigen / beziehen sich auf die say () -Methode des Hundes, da Dog ihr Objekttyp ist. Sofern die Methode [Hund sagt () "woof"] nicht definiert ist, wird sie zuerst im Klassenbaum gesucht, dh abgeleitete Klassen können Methoden ihrer Basisklassen überschreiben [Animal sagt ()].
Es ist interessant festzustellen, dass alle Klassenattribute (Daten und Methoden) in Python effektiv virtuell sind . Da alle Objekte zur Laufzeit dynamisch erstellt werden, ist weder eine Typdeklaration noch ein virtuelles Schlüsselwort erforderlich. Unten ist Pythons Version des Codes:
Die Ausgabe ist:
Dies ist identisch mit der virtuellen Definition von C ++. Beachten Sie, dass d und ad zwei verschiedene Zeigervariablen sind, die auf dieselbe Dog-Instanz verweisen. Der Ausdruck (ad is d) gibt True zurück und ihre Werte sind dieselben < main .Dog-Objekt bei 0xb79f72cc>.
quelle
Kennen Sie Funktionszeiger? Virtuelle Funktionen sind eine ähnliche Idee, außer dass Sie Daten einfach an virtuelle Funktionen binden können (als Klassenmitglieder). Es ist nicht so einfach, Daten an Funktionszeiger zu binden. Für mich ist dies die wichtigste konzeptionelle Unterscheidung. Viele andere Antworten hier sagen nur "weil ... Polymorphismus!"
quelle
Wir benötigen virtuelle Methoden zur Unterstützung von "Laufzeitpolymorphismus". Wenn Sie mit einem Zeiger oder einem Verweis auf die Basisklasse auf ein abgeleitetes Klassenobjekt verweisen, können Sie eine virtuelle Funktion für dieses Objekt aufrufen und die Version der Funktion der abgeleiteten Klasse ausführen.
quelle
Das Fazit ist, dass virtuelle Funktionen das Leben leichter machen. Lassen Sie uns einige von M Perrys Ideen verwenden und beschreiben, was passieren würde, wenn wir keine virtuellen Funktionen hätten und stattdessen nur Zeiger auf Elementfunktionen verwenden könnten. Wir haben nach normaler Schätzung ohne virtuelle Funktionen:
Ok, das wissen wir also. Versuchen wir nun, dies mit Zeigern auf Elementfunktionen zu tun:
Wir können zwar einige Dinge mit Zeigern auf Elementfunktionen tun, sie sind jedoch nicht so flexibel wie virtuelle Funktionen. Es ist schwierig, einen Elementfunktionszeiger in einer Klasse zu verwenden. Der Elementfunktionszeiger muss, zumindest in meiner Praxis, fast immer in der Hauptfunktion oder innerhalb einer Elementfunktion wie im obigen Beispiel aufgerufen werden.
Auf der anderen Seite vereinfachen virtuelle Funktionen, obwohl sie möglicherweise einen gewissen Funktionszeiger-Overhead haben, die Dinge dramatisch.
BEARBEITEN: Es gibt eine andere Methode, die eddietree ähnelt: virtuelle C ++ - Funktion vs. Elementfunktionszeiger (Leistungsvergleich) .
quelle