2014-10-03 4 views
5

Ich bin Java-Programmierer und habe vor kurzem begonnen, C++ zu lernen. Ich bin verwirrt von etwas.Warum kann ich keine Polymorphie mit normalen Variablen machen?

Ich verstehe, dass in C++, um polymorphes Verhalten zu erreichen, müssen Sie entweder Zeiger oder Referenzen verwenden. Betrachten Sie beispielsweise eine Klasse Shape mit einer implementierten Methode getArea(). Es hat mehrere Unterklassen, die getArea() unterschiedlich überschreiben. Als betrachten Sie die folgende Funktion:

void printArea(Shape* shape){ 
    cout << shape->getArea(); 
} 

Die Funktion ruft die korrekte getArea() Implementierung, basierend auf den konkreten Shape der Zeiger auf.

Dies funktioniert genauso:

void printArea(Shape& shape){ 
    cout << shape.getArea(); 
} 

Allerdings ist die folgende Methode polymorphicaly nicht:

void printArea(Shape shape){ 
    cout << shape.getArea(); 
} 

tut, was nicht Materie konkrete Art von Shape in der Funktion übergeben wird, die gleiche getArea() Implementierung heißt: der Standard in Shape.

Ich möchte die technische Begründung dahinter verstehen. Warum funktioniert Polymorphismus mit Zeigern und Referenzen, aber nicht mit normalen Variablen? (Und ich denke, das gilt nicht nur für Funktionsparameter, sondern für alles).

Bitte erläutern Sie die technischen Gründe für dieses Verhalten, um mir zu verstehen.

+0

Dies könnte hilfreich sein. http://stackoverflow.com/questions/7516545/why-is-object-slice-needed-in-c-why-it-is-allowed-for-more-bugs –

+5

Es könnte auch hilfreich sein, das in Java zu wissen Alle Objekte sind tatsächlich Zeiger auf reale Objekte. Wenn Sie also die Klasse Shape haben, kopiert "Shape a = b" intern nur den Zeiger auf das tatsächliche Objekt und inkrementiert seine interne Referenzzahl. Wenn alle Verweise auf ein Objekt nicht mehr vorhanden sind, unterliegt es der Garbage Collection von Java. In C++ können Sie mit den tatsächlichen Objekten spielen. Kopieren Sie sie herum, usw. Deshalb arbeitet Polymorphismus nur mit Zeigern (oder Referenzen, die meistens syntaktische Zucker für Zeiger sind). –

+0

Die Funktion erhält nur eine 'Form'. Wenn Sie einen Subtyp (oder einen anderen Typ) übergeben, wird dieser in eine 'Form' umgewandelt (zugewiesen). – Galik

Antwort

4

Die Antwort ist Kopie Semantik.

Wenn Sie passieren, ein Objekt durch einen Wert in C++, z.B. printArea(Shape shape) wird eine Kopie des übergebenen Objekts erstellt. Und wenn Sie eine abgeleitete Klasse an diese Funktion übergeben, wird nur die Basisklasse Shape kopiert. Wenn Sie darüber nachdenken, kann der Compiler nichts anderes tun.

Shape shapeCopy = circle; 

shapeCopy wurde als Shape erklärt, kein Circle, so dass alle der Compiler tun können, ist eine Kopie des Shape Teil des Objekts zu konstruieren.

+0

Ich verstehe. Und das gilt auch für die einfache Zuordnung, nicht nur zur Parameterübergabe, oder? Zum Beispiel im folgenden Code: 'Shape s; wenn (etwas) s = Rechteck (5, 5); sonst s = Kreis (5); cout << s.getArea(); 'Was in' s' zugewiesen wird, wird einfach auf eine 'Shape' reduziert (egal, ob es ein' Rectangle' oder ein 'Circle' ist, weil der Wert kopiert wird? –

+0

Korrektes Weiterleiten nach Wert bewirkt, dass eine Kopie erstellt wird, aber es geht nur darum, eine Kopie zu erstellen, und nicht speziell über die Übergabe eines Parameters. –

+0

Immer noch nicht sicher, warum der Compiler eine 'Circle' -Instanz nicht einer' Shape' -Variablen zuweisen kann - d. H. Warum sie es "schneiden" muss, bevor es der Variablen zugewiesen wird. Eine Annahme ist, dass es wegen des Speichers ist: der Compiler hat der Variablen eine geeignete Speichermenge für eine 'Form' zugewiesen, die nicht unbedingt zu einem' Kreis'-Objekt passt. Es reduziert also den "Kreis" auf die Definition einer "Form". Ist das korrekt? –

1

Wenn void printArea(Shape shape) aufgerufen wird, wird Ihr Objekt in einen brandneuen Shape auf dem Stapel kopiert. Die Unterklasse Teile werden nicht kopiert. Dies ist bekannt als object slicing. Wenn das Basisklassenobjekt nicht echt ist (z. B. hat es reine virtuelle Funktionen), können Sie diese Funktion nicht einmal deklarieren oder aufrufen. Das ist die Semantik "pass by value"; Eine Kopie des übergebenen Objekts wird bereitgestellt.

Wenn void printArea(Shape& shape) aufgerufen wird, wird ein Verweis auf Ihr Circle oder Rectangle Objekt übergeben. (Genauer gesagt, ein Verweis auf den Shape Teil dieses Objekts. Sie können nicht auf die Circle - oder Square -spezifischen Member ohne Casting zugreifen. Aber virtuelle Funktionen funktionieren natürlich, richtig.) Das ist "pass by reference" Semantik; ein Verweis auf das ursprüngliche Objekt wird übergeben.

+0

Streng genommen übergeben Sie im letzteren Fall wirklich einen Verweis auf ein 'Shape'-Objekt. Die interessante Frage ist, ob dieses 'Shape'-Objekt am meisten abgeleitet ist oder ob es ein Unterobjekt eines größeren Objekts ist. Das "Herausfinden, was deine Umgebung ist" ist die Sache, die deine Plattform durch eine geeignete Art von Magie implementieren muss. –

+0

Hinzugefügt eine Klammer in den Körper zur Klärung. Vielen Dank! – StilesCrisis

+0

Danke für die Antwort. Dennoch bin ich mir nicht sicher, warum der Compiler kein Circle-Objekt einer Shape-Variablen zuweisen kann - also warum er es "schneiden" muss, bevor es der Variablen zugewiesen wird. Eine Annahme, die ich habe, ist, dass es wegen des Gedächtnisses ist: der Compiler ordnete der Variablen eine geeignete Menge an Speicher für eine 'Form' zu, was nicht unbedingt zu einem Kreisobjekt passt. Es reduziert also den Kreis auf die Definition einer 'Form'. Ist das korrekt? –

1

Sie sprechen über Laufzeitpolymorphismus.

Dies ist die Idee, dass ein Objekt aus einer Klasse aus der statisch bekannten Klasse * abgeleitet werden kann, so dass in der statisch bekannten Klasse virtuelle Mitgliedsfunktionen aufruft, in abgeleiteten Klasse Implementierungen enden.

Mit einem Zeiger oder einer Referenz kann die am weitesten abgeleitete Klasse von der (spezifischeren) statisch bekannten Klasse abweichen. Bei einer direkten Variablen ist die statisch bekannte Klasse die am weitesten abgeleitete Klasse. Daher gibt es keinen Platz für Laufzeitpolymorphie, außer in Aufrufen, die Aufrufe von Basisklassenmemberfunktionen verursachen (in einer Basisklassenmemberfunktion ist der statisch bekannte Typ diese Basisklasse, die sich von der abgeleiteten Klasse unterscheidet).


*) Statisch bekannt Klasse Mittel ohne jede Analyse, die deklarierte Klasse des Compiler bekannt.

+0

Ich weiß nicht, ob jeder auf dieser Ebene versteht, was Sie mit "statisch bekannten Klasse" meinen. Darüber hinaus gibt das OP einen Java-Hintergrund an, so dass die C++ - Wert/Kopie-Semantik möglicherweise unbekannt ist. Von einem etwas anderen Standpunkt aus könnte man über den Typ des Objekts sprechen, das tatsächlich im Speicher existiert, und über den Typ, den der Compiler sieht, wenn er den Funktionsparameter betrachtet. – dyp

+0

okai, fügte eine Terminologie-Fußnote hinzu. Vielen Dank! –

0

Wenn Sie ein Objekt nach Wert übergeben, wird es in ein Objekt des Zieltyps kopiert - zu diesem Zeitpunkt ist ein Objekt des Zieltyps, also gibt es nichts zu polymorph (ist das überhaupt ein Wort?) .

Ihr Beispiel zu verwenden, void printArea(Shape shape);, innerhalb der printArea() Funktion des Parameter shape ein Shape Objekt ist, unabhängig davon, welches Objekt an der Aufrufstelle verwendet wurde.

Verwandte Themen