Verwirrung über virtual / new / override

8

Ich bin ein bisschen verwirrt über die virtual / new / override Sache. Hier ist ein Beispiel:

%Vor%

Ich verstehe nicht, warum wir im zweiten Aufruf A::mVVirtual bekommen. Ich behandle diese Probleme normalerweise mit diesem "Algorithmus":

  1. Überprüfen Sie den Typ der Variablen, die die Referenz für das Objekt für eine Instanzmethode namens mVVirtual enthält? Hat keinen ... aber hat eine virtuelle Methode mit dieser Unterschrift und diesem Namen!
  2. Virtuelle Methode? Lassen Sie uns dann den Typ des Objekts überprüfen, das von a2 ( C ) für ein Überschreiben dieser Methode gehalten wird. Es hat eine - & gt; Führt C::mVVirtual !
  3. aus

Wo ist mein "Algorithmus" falsch? Ich bin wirklich verwirrt und würde eine Hilfe sehr schätzen.

    
Bruno 14.01.2010, 16:40
quelle

5 Antworten

11

So denken Sie über virtuelle Methoden nach. Jede Instanz einer Klasse hat "Boxen", um Methoden zu speichern. Wenn Sie eine Methode als virtual markieren, wird eine neue "Box" erstellt und eine Methode eingefügt. Wenn Sie eine Methode als override in einer abgeleiteten Klasse markieren, behält sie die "Box" aus der Basisklasse, fügt aber eine neue Methode ein.

Hier haben Sie also eine Klasse A und eine Methode namens mVVirtual , die als virtual markiert ist. Dies sagt, machen Sie eine neue "Box" mit dem Namen mVVirtual und fügen Sie eine Methode mit der Definition

hinzu %Vor%

Dann haben Sie eine abgeleitete Klasse B und eine Methode namens mVVirtual , die als virtual markiert ist. Dies sagt, machen Sie eine neue "Box" mit dem Namen mVVirtual und fügen Sie eine Methode mit der Definition

hinzu %Vor%

Insbesondere ist die von A geerbte "Box" versteckt! Objekte, die als B s oder Klassen, die von B abgeleitet sind, werden nicht angezeigt.

Dann haben Sie eine abgeleitete Klasse C und eine Methode namens mVVirtual , die als override markiert ist. Das heißt, nehmen Sie die "Box" mit dem Namen mVVirtual geerbt von B und legen Sie eine andere Methode mit der Definition

%Vor%

Nun, wenn Sie

haben %Vor%

Sie sagen dem Compiler, dass b1 ein B ist, so dass b1.mVVirtual() in der "Box" mVVirtual aussieht und die Methode mit der Definition

findet %Vor%

weil b1 wirklich ein C ist und das ist, was in der "box" mVVirtual für Instanzen von C ist.

Aber wenn du

hast %Vor%

Sie sagen dem Compiler, dass a2 ein A ist und so sieht es in der "Box" aus und findet

%Vor%

Der Compiler kann nicht wissen, dass a2 wirklich ein C ist (Sie haben ihn als A eingegeben), also weiß er nicht, dass a2 wirklich eine Instanz einer Klasse ist, von der abgeleitet wird Eine Klasse, die die "Box" mVVirtual , die von A definiert wurde, ausgeblendet hat. Was es weiß, ist, dass A eine "Box" namens mVVirtual hat und so Code ausgibt, um die Methode in dieser "Box" aufzurufen.

Also, um dies kurz und bündig zu sagen:

%Vor%

definiert eine Klasse, die eine "Box" mit dem vollständigen Namen A::mVVirtual hat, auf die Sie jedoch mit dem Namen mVVirtual verweisen können.

%Vor%

definiert eine Klasse, die eine "Box" mit dem vollständigen Namen B::mVVirtual hat, auf die Sie jedoch mit dem Namen mVVirtual verweisen können. Der Verweis auf B.mVVirtual bezieht sich nicht auf die "Box" mit dem vollständigen Namen A::mVVirtual ; Diese "Box" kann nicht von Objekten gesehen werden, die als B s (oder Klassen, die von B abgeleitet sind) eingegeben werden.

%Vor%

definiert eine Klasse, die die "Box" mit dem vollständigen Namen B::mVVirtual übernimmt und eine andere Methode darin einfügt.

Dann

%Vor%

sagt, dass a2 ein A ist, so dass a2.mVVirtual in der "box" mit dem vollständigen Namen A::mVVirtual aussieht und die Methode in dieser "box" aufruft. Deshalb sehen Sie

%Vor%

auf der Konsole.

Es gibt zwei weitere Methodenannotatoren. abstract bewirkt, dass eine neue "Box" keine Methodendefinition in die "Box" schreibt. new erstellt eine neue "Box" und fügt eine Methodendefinition in die "Box" ein, erlaubt aber nicht, dass abgeleitete Klassen ihre eigenen Definitionen der Methode in die "Box" einfügen (benutze virtual , wenn du das willst) .

Entschuldige, dass ich langatmig bin, aber ich hoffe, das hilft.

    
jason 14.01.2010 17:23
quelle
6

UPDATE: Weitere Informationen zu dieser Sprachfunktion finden Sie in der nachfolgenden Frage: Mehr über Virtuell / Neu ... plus Schnittstellen!

Jasons Antwort ist richtig. Um es kurz zusammenzufassen.

Sie haben drei Methoden. Nennen Sie sie MA, MB und MC.

Sie haben zwei "Boxen", oder wie sie normalerweise genannt werden, Slots. Wir bleiben bei Jasons Nomenklatur. Nennen Sie sie BOX1 und BOX2.

"A" definiert BOX1.

"B" definiert BOX2.

"C" definiert keine Box; Es verwendet BOX2.

Wenn Sie "new A ()" sagen, wird BOX1 mit MA ausgefüllt.

Wenn Sie "new B ()" sagen, wird BOX1 mit MA und BOX2 mit MB gefüllt.

Wenn Sie "new C ()" sagen, wird BOX1 mit MA und BOX2 mit MC gefüllt.

Angenommen, Sie haben eine Variable vom Typ A und einen Aufruf der Methode. Grund wie der Compiler. Der Compiler sagt "Gibt es irgendwelche Felder auf Typ A, die diesem Namen entsprechen?" Ja, es gibt einen: BOX1. Daher generiert der Compiler einen Aufruf an den Inhalt von BOX1.

Wie wir gesehen haben, ist der Inhalt von BOX1 immer MA, daher wird MA immer aufgerufen, egal, ob die Variable tatsächlich einen Verweis auf A, B oder C enthält.

Nehmen wir nun an, Sie haben eine Variable vom Typ B und einen Aufruf der Methode. Denken Sie wieder wie der Compiler. Der Compiler sagt "Gibt es irgendwelche Felder auf Typ B, die diesem Namen entsprechen?" Ja, es gibt ZWEI Boxen, die nach Namen übereinstimmen. Der Compiler sagt "welcher dieser beiden ist näher mit B verbunden?" Die Antwort ist BOX2, weil B BOX2 deklariert. Daher generiert der Compiler einen Aufruf an BOX2.

Dies ruft MB auf, wenn die Variable ein B enthält, weil BOX2 in einem B MB enthält. Dies ruft MC auf, wenn die Variable ein C enthält, weil in BOX2 MC enthalten ist.

Ist das jetzt klar? Denken Sie daran, Überladungsauflösung wählt nur das Feld aus. Was der Inhalt der Box ist, hängt von dem Objekt zur Laufzeit ab.

    
Eric Lippert 14.01.2010 18:16
quelle
3

Haben Sie Warnungen versteckt? Wenn ich das mache, was Sie getan haben, bekomme ich diese Warnung:

  

'ProjectName.ClassName.BmVVirtual ()' verbirgt das geerbte Element 'ProjectName.ClassName.A.mVVirtual ()'. Fügen Sie das Schlüsselwort override hinzu, damit das aktuelle Mitglied diese Implementierung überschreibt. Andernfalls füge das neue Schlüsselwort hinzu.

Wenn Sie override in Klasse B verwendet hätten, hätten Sie dieses Problem nicht; In beiden Fällen erhalten Sie "C :: mVVirtual". Da Sie override nicht in Klasse B verwenden, befindet sich vor der Methode ein implizites new . Dies unterbricht die Vererbungskette. Ihr Code ruft eine Methode für Typ A auf und es gibt keine erbenden Klassen, die diese Methode aufgrund der impliziten new überschreiben. Also muss es die Implementierung von Klasse A aufrufen.

    
Ryan Lundy 14.01.2010 16:57
quelle
1

Der beste Weg, um darüber nachzudenken, ist, dass virtuelle Methoden den tatsächlichen (oder konkreten ) -Typ verwenden das Objekt, um zu entscheiden, welche Implementierung ausgeführt werden soll, wobei nicht-virtuelle Methoden den 'deklarierten Typ der Variablen verwenden, die Sie verwenden, um auf die Methode zuzugreifen, um zu entscheiden, welche ausgeführt werden soll ...

Überschreiben bedeutet, dass Sie eine Methode schreiben, die die Implementierung für eine virtuelle oder abstrakte Methode (mit demselben Namen / derselben Signatur) in der Vererbungskette "ersetzt".

new wird verwendet, wenn es eine nicht-virtuelle Methode in der Kette mit demselben Namen / derselben Signatur gibt, die die Methode, die Sie hinzufügen, ersetzt ...

Der Unterschied ist wie folgt

%Vor%

aber

%Vor%

Ihr zweiter Aufruf gibt A::mvVirtual aus, weil diese Methode ( mVVirtual ) tatsächlich nicht virtuell ist (trotz ihres Namens), weil sie das < em> new specifier ... Also entscheidet er basierend auf dem Variablentyp, der A ist.

Um zu erklären, was technisch vor sich geht, hat jeder Typ eine "Methodentabelle" mit Zeigern auf alle Methoden in diesem Typ. (Es ist NICHT die Instanz eines Typs, der diese Tabelle hat. Es ist TYPE selbst.) Die Methodentabelle jeder Tabelle ist zuerst mit allen zugänglichen virtuellen Methoden strukturiert object (am weitesten oben die Vererbungskette) am Anfang zu den virtuellen Methoden, die im Typ selbst deklariert sind, am Ende. Nachdem alle virtuellen Methoden dargestellt sind, werden alle nicht virtuellen Methoden erneut aus allen nicht virtuellen Methoden in object hinzugefügt, zuerst bis hin zu allen nicht virtuellen Methoden im Type selbst. Die Tabelle ist so strukturiert, dass die Offsets für alle virtuellen Metoden in allen Methodentabellen abgeleiteter Klassen identisch sind, da der Compiler diese Methoden von Variablen aufrufen kann, die als andere Typen deklariert sind, sogar von Code in anderen Methoden, die in base deklariert und implementiert werden Arten der konkreten Klasse.

Wenn der Compiler einen Aufruf einer virtuellen Methode auflöst, geht er in die Methodentabelle für den Typ des Objekts selbst (der konkrete Typ), während für eine nicht virtueller Aufruf Es wird in die Methodentabelle für deklarierten Typ der Variablen verzweigt. Wenn Sie also eine virtuelle Methode aufrufen, selbst wenn es sich um einen Code in einem Basistyp handelt, wenn der tatsächliche konkrete Typ von diesem Basistyp abgeleitet ist, springt der Compiler zur Methodentabelle für diesen konkreten Typ.

Wenn Sie eine nicht-virtuelle Methode aufrufen (egal, wie weit die Vererbung sich ändert, kann der Typ des tatsächlichen Objekts sein), Der Compiler greift auf die Methodentabelle für den deklarierten Typ der Variablen zu. Diese Tabelle enthält nothing aus allen abgeleiteten Typen, die sich in der Kette befinden.

    
Charles Bretana 14.01.2010 16:46
quelle
0

So verstehe ich es

A ist die Basisklasse
B erbt A, überschreibt es aber nicht C erbt B aber überschreibt es

Da Sie A deklarieren, aber C initialisieren, ignoriert es die Überschreibung, weil die Basisklasse A und A niemals von B überschrieben wird.

    
Fredou 14.01.2010 17:17
quelle