9. Vererbung und Polymorphismus



Ähnliche Dokumente
Einführung in C++ Vererbung und Polymorphismus

1. Grundlegende Eigenscha5en 2. Redefini+on 3. Polymophie 4. Mehrfachvererbung

Programmierung und Angewandte Mathematik

Programmieren in Java

Java: Vererbung. Teil 3: super()

Vererbung & Schnittstellen in C#

Objektorientierte Programmierung

Folge 18 - Vererbung

Wintersemester Maschinenbau und Kunststofftechnik. Informatik. Tobias Wolf Seite 1 von 22

Vorkurs C++ Programmierung

12. Vererbung. Prof. Dr. Markus Gross Informatik I für D-ITET (WS 03/04)

Lineargleichungssysteme: Additions-/ Subtraktionsverfahren

Verhindert, dass eine Methode überschrieben wird. public final int holekontostand() {...} public final class Girokonto extends Konto {...

Klassenbeziehungen & Vererbung

Einführung in die Java- Programmierung

Einführung in die Programmierung (EPR)

Objektorientierte Programmierung. Kapitel 12: Interfaces

Einführung in die Programmierung

2. Semester, 2. Prüfung, Lösung

Einführung in die Programmierung

Fachgebiet Informationssysteme Prof. Dr.-Ing. N. Fuhr. Programmierung Prof. Dr.-Ing. Nobert Fuhr. Übungsblatt Nr. 6

AGROPLUS Buchhaltung. Daten-Server und Sicherheitskopie. Version vom b

Übungen zu Einführung in die Informatik: Programmierung und Software-Entwicklung: Lösungsvorschlag

Unterprogramme. Funktionen. Bedeutung von Funktionen in C++ Definition einer Funktion. Definition einer Prozedur

Java Kurs für Anfänger Einheit 4 Klassen und Objekte

Objektorientierte Programmierung für Anfänger am Beispiel PHP

Objektorientierte Programmierung mit C++ Vector und List

II. Grundlagen der Programmierung. 9. Datenstrukturen. Daten zusammenfassen. In Java (Forts.): In Java:

Ziel, Inhalt. Programmieren in C++ Wir lernen wie man Funktionen oder Klassen einmal schreibt, so dass sie für verschiedene Datentypen verwendbar sind

Kapitel 6. Vererbung

Prinzipien Objektorientierter Programmierung

Primzahlen und RSA-Verschlüsselung

GetName(), GetName(), GetGeschlecht() und AelterWerden().

3 Objektorientierte Konzepte in Java

Kapitel 6. Vererbung

5. Tutorium zu Programmieren

Java Kurs für Anfänger Einheit 5 Methoden

C++ - Einführung in die Programmiersprache Polymorphismus und Vererbung. Eltern

Das Typsystem von Scala. L. Piepmeyer: Funktionale Programmierung - Das Typsystem von Scala

Arbeiten mit UMLed und Delphi

Zwischenablage (Bilder, Texte,...)

5 DATEN Variablen. Variablen können beliebige Werte zugewiesen und im Gegensatz zu

Grundlagen von Python

1 Mathematische Grundlagen

Einführung in die objektorientierte Programmierung mit Java. Klausur am 19. Oktober 2005

Klausur in Programmieren

Zählen von Objekten einer bestimmten Klasse

1 Vom Problem zum Programm

1 topologisches Sortieren

Algorithmen und Datenstrukturen

5. Abstrakte Klassen. Beispiel (3) Abstrakte Klasse. Beispiel (2) Angenommen, wir wollen die folgende Klassenhierarchie implementieren:

Deklarationen in C. Prof. Dr. Margarita Esponda

Computeranwendung und Programmierung (CuP)

Ein Blick voraus. des Autors von C++: Bjarne Stroustrup Conrad Kobsch

Kapitel 6. Vererbung

4 Vererbung, Polymorphie

Factory Method (Virtual Constructor)

Programmieren in C. Macros, Funktionen und modulare Programmstruktur. Prof. Dr. Nikolaus Wulff

Abschnitt 9: Schnittstellen: Interfaces

13 OOP MIT DELPHI. Records und Klassen Ein Vergleich

Software Engineering Klassendiagramme Assoziationen

Die Programmiersprache C99: Zusammenfassung

Typumwandlungen bei Referenztypen

Angewandte Mathematik und Programmierung

Programmierkurs Java

Anleitung über den Umgang mit Schildern

5. Abstrakte Klassen

Funktionen Häufig müssen bestimmte Operationen in einem Programm mehrmals ausgeführt werden. Schlechte Lösung: Gute Lösung:

L10N-Manager 3. Netzwerktreffen der Hochschulübersetzer/i nnen Mannheim 10. Mai 2016

Zur drittletzten Zeile scrollen

Professionelle Seminare im Bereich MS-Office

1. Übung zu "Numerik partieller Differentialgleichungen"

Der Aufruf von DM_in_Euro 1.40 sollte die Ausgabe 1.40 DM = Euro ergeben.

3. Konzepte der objektorientierten Programmierung

4. BEZIEHUNGEN ZWISCHEN TABELLEN

Delegatesund Ereignisse

Javakurs zu Informatik I. Henning Heitkötter

Software-Engineering und Optimierungsanwendungen in der Thermodynamik

M. Graefenhan Übungen zu C. Blatt 3. Musterlösung

Objektbasierte Entwicklung

Der lokale und verteilte Fall

Objektorientierung: Klassen und Objekte

Menü auf zwei Module verteilt (Joomla 3.4.0)

Mediator 9 - Lernprogramm

Binäre Bäume. 1. Allgemeines. 2. Funktionsweise. 2.1 Eintragen

Wie Sie mit Mastern arbeiten

Testen mit JUnit. Motivation

Modellierung und Programmierung 1

Gutes Leben was ist das?

Objektorientierte Programmierung OOP

Arge Betriebsinformatik GmbH & Co.KG, CAP News 40, Februar CAP-News 40

Einführung in die C++ Programmierung für Ingenieure

Pass by Value Pass by Reference Defaults, Overloading, variable Parameteranzahl

Erstellen einer Collage. Zuerst ein leeres Dokument erzeugen, auf dem alle anderen Bilder zusammengefügt werden sollen (über [Datei] > [Neu])

Rundung und Casting von Zahlen

Kulturelle Evolution 12

Proseminar C-Programmierung. Strukturen. Von Marcel Lebek

Objects First With Java A Practical Introduction Using BlueJ. Mehr über Vererbung. Exploring polymorphism 1.0

Innere Klassen in Java

Abschnitt 12: Strukturierung von Java-Programmen: Packages

Transkript:

C und C++ (CPP) 9. Vererbung und Polymorphismus Prof. Dr. Marc Rennhard Institut für angewandte Informationstechnologie InIT ZHAW Zürcher Hochschule für angewandte Wissenschaften marc.rennhard@zhaw.ch Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 1 1

Ablauf Grundlegende Eigenschaften der Vererbung in C++ Eine einfache Klassenhierarchie in Java und C++ Method Overriding Polymorphismus Dynamic und Static Casts Method Overloading in Subklassen und Namensauflösung Virtual Destructors Operator Overloading und Vererbung Mehrfachvererbung Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 2 2

Ziele Sie wissen, was Vererbung prinzipiell ist und wie Vererbung in C++ konkret funktioniert Sie wissen, was Method Overriding bedeutet Sie verstehen wie Polymorphismus in C++ funktioniert und welchen Einfluss dabei virtuelle Methoden haben Sie verstehen das Konzept von Dynamischen und Statischen Casts und können diese Casts selbst anwenden Sie verstehen die grundlegende Idee von Mehrfachvererbung, die potentiellen Probleme, die dabei auftauchen, und wie man mit diesen Problemen umgeht Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 3 3

Einführung (1) Wie in Java: In C++ können Klassen erweitert/abgeleitet werden Vererbung Superklasse: Klasse, von welcher vererbt wird Superklasse Subklasse: die abgeleitete Klasse Von einer Subklasse können wiederum Subklasse Subklassen abgeleitet werden Begriffe Superklasse und Subklasse sind relativ Keine Interfaces in C++ Mehrfachvererbung in C++: eine Klasse kann von mehr als einer Klasse abgeleitet werden Eine Subklasse erbt alle public und protected (nicht aber private) Methoden und Variablen aller ihrer Superklassen (auch der indirekten Superklassen) Subklassen können eine Methode einer Superklasse überschreiben und damit die Methode der Superklasse in der Subklasse überdecken Method Overriding Gilt auch für Instanzvariablen und statische Variablen Attribute Overriding Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 4 Die prinzipielle Idee hinter Vererbung kennen Sie bereits aus den Vorlesungen über Java, ist für alle OO-Sprachen dieselbe und wird hier nicht detailliert wiederholt. Nur zur Erinnerung: Die Idee, die hinter dem Prinzip der Vererbung steht, besagt, dass es sinnvoller und vor allem rationeller ist, vorhandene und getestete Teile alter Programme für neue Programme zu verwenden, statt jedes Mal alles neu zu schreiben. Dabei muss es möglich sein, die schon bestehenden Programmteile den neuen Aufgaben anzupassen und zwar ohne den eigentlichen Programmcode abzuändern, der vielleicht gar nicht zur Verfügung steht. Das Prinzip gilt auch nicht nur, um Code eines anderen Programms wieder zu verwenden, sondern auch innerhalb eines Programms, wo einzelne spezialisierte Programmteile häufig von anderen, grundlegenderen Programmteilen abhängen und deshalb der Gebrauch von Vererbung sinnvoll ist. Dabei wird der grundlegende Teil des Programms in einer Klasse implementiert und verschiedene spezialisierte Teile in weiteren Klassen implementiert werden, die alle von der Grundklasse abgeleitet werden. Dabei kann eine Subklasse wiederum selbst Subklassen haben, wodurch eigentliche Klassenhierarchien entstehen. Grösserer OO-Programme bestehen eigentlich immer aus einer grossen Anzahl von Klassen, wobei viele dieser Klassen von anderen Klassen im Programm abgeleitet sind. 4

Einführung (2) C++ unterstützt wie Java Polymorphismus: eine Variable vom Typ einer Superklasse kann irgendein Objekt vom Typ einer Subklasse beinhalten Generell gilt: Vererbung und Polymorphismus werden flexibler gehandhabt als in Java, sind aber auch anfälliger für Programmierfehler Beispiel: in Java ist eine Objektvariable immer polymorph, in C++ wird dies vom Programmierer kontrolliert Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 5 5

Vererbung (1) Ableiten einer Klasse mit Doppelpunkt : und Visibility Modifier: Java: class B extends A {... C++: class B : public A {... Alle public und protected Methoden und Variablen werden vererbt und sind dadurch auch in der Subklasse vorhanden Diese Sichtbarkeit kann mit dem Visibility-Modifier nach dem Doppelpunkt weiter eingeschränkt werden: Variable/Methode in Superklasse public protected private Subklasse vererbt als public protected private (Def.) Variable/Methode in Subklasse public protected private protected protected private nicht vererbt nicht vererbt nicht vererbt Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 6 Man beachte, dass der Default Visibility Modifier private ist und nicht public auch wenn man in den allermeisten Fällen public benötigt. Java kennt diese weitere Einschränkung mittels Visibility Modifier bei der Vererbung nicht es wird immer public vererbt. Weil prinzipiell von jeder entwickelten Klasse Subklassen abgeleitet werden können, sollten Sie bei der Entwicklung Variablen und Methoden wenn möglich protected statt private spezifizieren - ausser die entsprechende Variable/Methode soll wirklich nicht in einer Subklasse verfügbar sein. 6

Vererbung (2) Beispiel zu private / protected / public Vererbung: class A { private: int priv; protected: int prot; int publ; class B : pxxx A { void m() {... void f() { A a; B b; Zugriff von aussen (f()) a.priv++; a.prot++; a.publ++; b.priv++; b.prot++; pxxx = private nein nein ja nein nein pxxx = protected nein nein ja nein nein pxxx = public nein nein ja nein nein Zugriff innerhalb B (m()) priv++; prot++; publ++; unabhängig von pxxx nein ja ja b.publ++; nein nein ja Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 7 7

Vererbung (3) private / protected Vererbung: alle public Methoden der Superklasse sind in der Subklasse nicht mehr public oft eine zu starke Einschränkung Abhilfe: explizite Wiederimplementierung einiger Methoden in der Subklasse, wobei diese Methoden explizit die Methoden der Superklasse aufrufen class A { void func1() { void func2() { class B : protected A { void func2() {A::func2(); int main() { B b; b.func1(); // Kompilierfehler, protected b.func2(); // Ok, public Der Scope Resolution Operator :: wird allgemein dann gebraucht, wenn expizit auf eine Klasse zugegriffen werden soll private/protected Vererbung wird selten verwendet evtl. ein Hinweis auf unsauberes OO- Design! Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 8 Generell wird private/protected Vererbung in der Praxis selten verwendet; dementsprechend wurde es in Java auch weggelassen. Auch vom OO-Standpunkt her gesehen ist private/protected Vererbung nicht sauber, weil dann die Subklasse die (nicht privaten) Methoden der Superklasse nicht einfach unverändert erbt (wie es bei sauberem OO-Design sein soll), sondern deren Sichtbarkeit verändert. Wenn Sie also einmal zu private/protected Vererbung greifen sollten, dann sollten Sie sich fragen, ob die Subklasse wirklich von dieser Superklasse abgeleitet werden soll, oder ob nicht in Ihrem Klassendiagramm ein Schönheitsfehler drin steckt. Mit sauberem OO- Design sollten Sie private/protected Vererbung eigentlich nicht verwenden müssen. Ein weiteres Problem mit nicht-public Vererbung ist, dass es mit Polymorphismus nicht kompatibel ist. So funktionieren bei Verwendung der obigen Klassen die folgenden Zuweisungen nicht, da B von der Klasse A mit Visibility protected abgeleitet wird: A a1, *a2; B b; a1 = b; // Kompilierfehler a2 = &b; // Kompilierfehler 8

Vererbung (4) Constructors werden nicht vererbt, es existiert Constructor Chaining (wie Java) Ist B eine Subklasse von A, dann passiert bei der Initialisierung einer Instanz der Klasse B folgendes: Zuerst wird der Default Constructor der Klasse A aufgerufen Dann wird der (der Initialisierung) entsprechende Constructor der Klasse B aufgerufen Um einen anderen als den Default Constructor der Klasse A aufzurufen muss dies im Constructor der Klasse B mit einem Initializer explizit angegeben werden (Java: super(...)) Existiert der Default oder explizit angegebene Constructor in der Klasse A nicht, gibt es einen Fehler beim Kompilieren Destructors werden ebenfalls nicht vererbt Destructors werden in umgekehrter Reihenfolge zu den Constructors aufgerufen (zuerst der Destructor von Klasse B, dann der von Klasse A) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 9 Bei mehrstufiger Vererbung werden zuerst die Default Constructor aller Superklassen aufgerufen, und zwar von oben nach unten. Ist also C eine Subklasse von B und B eine Subklasse von A, so wird erst der Default Constructor von A, dann der von B und dann der der Initialisierung entsprechende Constructor von C ausgeführt. Die Reihenfolge bei Constructor Chaining macht Sinn, denn dadurch werden die durch Vererbung voneinander abhängigen Objekte in der richtigen Reihenfolge initialisiert. Aus dem gleichen Grund macht die umgekehrte Reihenfolge bei den Destructors Sinn. 9

Vererbung (5) Beispiel zu Constructor Chaining: #include <iostream> // constructor1.cpp using namespace std; class A { A() {cout << "Constructor A()" << endl; A(int v) {cout << "Constructor A(" << v << ")" << endl; class B : public A { B() {cout << "Constructor B()" << endl; B(int v) {cout << "Constructor B(" << v << ")" << endl; int main() { A a; // Ausgabe: Constructor A() B b; // Ausgabe: Constructor A() Constructor B() A c(3); // Ausgabe: Constructor A(3) B d(5); // Ausgabe: Constructor A() Constructor B(5) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 10 10

Vererbung (6) Beispiel zu Constructor Chaining mit Initializer: #include <iostream> // constructor2.cpp using namespace std; class A { A() {cout << "Constructor A()" << endl; A(int v) {cout << "Constructor A(" << v << ")" << endl; class B : public A { B() {cout << "Constructor B()" << endl; B(int v) : A(v) {cout << "Constructor B(" << v << ")" << endl; int main() { A a; // Ausgabe: Constructor A() B b; // Ausgabe: Constructor A() Constructor B() A c(3); // Ausgabe: Constructor A(3) B d(5); // Ausgabe: Constructor A(5) Constructor B(5) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 11 11

Eine einfache Klassenhierarchie Um Vererbung und Polymorphismus im Detail zu besprechen, verwenden wir die folgende Klassenhierarchie: Auto Japanisches Auto Italienisches Auto Deutsches Auto Mazda Nissan Ferrari Fiat BMW Opel VW Wir beschränken uns auf die gelben Klassen Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 12 12

Implementierung in Java // AutoProg.java abstract class Auto { abstract public void marke(); class ItalienischesAuto extends Auto { public void marke() {System.out.println("Macchina Italiana"); public void fahre250() {System.out.println("geht nicht"); class Fiat extends ItalienischesAuto { public void marke() {System.out.println("Fiat"); class Ferrari extends ItalienischesAuto { public void marke() {System.out.println("Ferrari"); public void fahre250() {System.out.println("250!"); Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 13 13

Implementierung in C++ // autoprog.cpp class Auto { virtual void marke() = 0; class ItalienischesAuto : public Auto { virtual void marke() {cout << "Macchina Italiana" << endl; void fahre250() {cout << "geht nicht" << endl; class Fiat : public ItalienischesAuto { virtual void marke() {cout << "Fiat" << endl; class Ferrari : public ItalienischesAuto { virtual void marke() {cout << "Ferrari" << endl; void fahre250() {cout << "250!" << endl; Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 14 14

Method Overriding (1) Method Overriding: Eine Subklasse implementiert eine Methode einer Superklasse neu (identischer Methodenkopf ist notwendig) Auto-Beispiel: class Auto { virtual void marke() = 0; class ItalienischesAuto : public Auto { virtual void marke() {cout << "Macchina Italiana" << endl; void fahre250() {cout << "geht nicht" << endl; class Fiat : public ItalienischesAuto { virtual void marke() {cout << "Fiat" << endl; class Ferrari : public ItalienischesAuto { virtual void marke() {cout << "Ferrari" << endl; void fahre250() {cout << "250!" << endl; Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 15 Identischer Methodenkopf heisst, dass sowohl Sichtbarkeit, Rückgabewert, Methodenname und die Datentypen der Methodenparameter gleich sein müssen. Beim Auto-Beispiel wird die Methode marke von Auto in ItalienischesAuto überschrieben, welche dann wiederum in den Klassen Fiat und Ferrari überschrieben werden. fahre250 ist zum ersten mal in Italienisches Auto definiert und wird in der Klasse Ferrari überschrieben, nicht aber in der Klasse Fiat. 15

Method Overriding (2) Wird eine Methode überschrieben, ist die Methode der Superklasse in einer Instanz der Subklasse nicht mehr sichtbar und es wird die neue Version der Methode verwendet: Ferrari f = new Ferrari(); // Java ItalienischesAuto ia = new ItalienischesAuto(); ia.marke(); // Macchina Italiana ia.fahre250(); // geht nicht f.marke(); // Ferrari f.fahre250(); // 250! Ferrari f; // C++ ItalienischesAuto ia; ia.marke(); // Macchina Italiana ia.fahre250(); // geht nicht f.marke(); // Ferrari f.fahre250(); // 250! Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 16 Wenn in der Subklasse auf eine überschriebene Methode/Variable der Superklasse zugegriffen werden soll, kann man in C++ den Scope resolution Operator :: verwenden. Aus der Klasse Ferrari kann damit auf die marke() Methode von ItalienischesAuto zugegriffen werden: ItalienischesAuto::marke(). Die entspricht in etwa der Funktionalität, die Sie mit dem Keyword super in Java erreichen können. 16

Polymorphismus (1) Eines der wichtigsten Konzepte der objektorientierten Programmierung Polymorphismus: eine Variable vom Typ einer Superklasse kann irgendein Objekt vom Typ einer Subklasse beinhalten (geht umgekehrt nicht!) Anders gesagt: der statische Typ (der bei der Deklaration angegebene Typ) einer Variablen kann sich von dem dynamischen Typ (der Typ des Werts, den die Variable gerade enthält) unterscheiden Auto-Beispiel: Eine Variable vom Typ ItalienischesAuto (statischer Typ)......kann eine Variable vom Typ Ferrari (dynamischer Typ) enthalten Der Zusammenhang zwischen Polymorphismus und Method Overriding ist ein wichtiger Aspekt von objektorientierten Programmiersprache In Java wird immer die Methode des dynamischen Typs angewendet In C++ wird das Verhalten durch den Programmierer bestimmt Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 17 Der statische Typ einer Variable ist der, mit welchem sie deklariert wurde. Ferrari f; der statische Typ von f ist Ferrari. 17

Polymorphismus (2) Polymorphismus in Java: Ferrari f = new Ferrari(); f.marke(); // Ferrari f.fahre250(); // 250! ItalienischesAuto ia = f; ia.marke(); // Ferrari ia.fahre250(); // 250! Java: Obwohl der statische Typ von ia ItalienischesAuto ist, werden die Methoden des dynamischen Typs (Ferrari) verwendet! In Java ist das immer so und kann auch nicht vom Programmierer beeinflusst werden Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 18 Natürlich muss der statische Typ (hier Italienisches Auto) die aufgerufene Methode selbst zur Verfügung stellen, entweder indem er sie selbst implementiert (wie bei fahre250()) oder indem er sie erbt. Falls dies nicht der Fall ist und ItalienischesAuto zb die Methode fahre250() nicht implementieren, so würde die letzte Zeile im Programm oben zu einem Kompilierfehler führen. 18

Polymorphismus (3) Polymorphismus in C++ mit Zuweisung von Objekten: Ferrari f; f.marke(); // Ferrari f.fahre250(); // 250! ItalienischesAuto ia; ia = f; ia.marke(); // Macchina Italiana ia.fahre250(); // geht nicht Werden Objekte einender zugewiesen, so werden immer die Methoden des statischen Typs (ItalienischesAuto) verwendet in diesem Fall existiert gar kein Polymorphismus Bei der Zuweisung werden keine Pointer kopiert, sondern immer die Objekte selbst Weil ein Objekt vom Typ Ferrari in einem Objekt vom Typ ItalienischesAuto keinen Platz hat, fallen die Erweiterungen der Klasse Ferrari weg Dadurch wird das Objekt in ein ItalienischesAuto konvertiert und verhält sich auch genau so Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 19 Erklärung: Für die Variablen f und ia auf dem Stack wird der Speicherplatz bereits zur Kompilierzeit bestimmt Bei der Zuweisung ia = f muss also die grössere Variable f (Ferrari ist ja eine Erweiterung von ItalienischesAuto) im Speicherplatz der kleineren Variablen ia abgespeichert werden Dabei wird im Wesentlichen diese Erweiterung einfach abgeschnitten (slicing), womit die neue Information von f (zum Beispiel die überschriebenen Methoden) gar nicht verwendet werden können 19

Polymorphismus (4) Polymorphismus in C++ mit Zuweisung von Pointern: Ferrari f; f.marke(); // Ferrari f.fahre250(); // 250! ItalienischesAuto *ia = &f; ia->marke(); // Ferrari ia->fahre250(); // geht nicht Werden statt Objekte Pointer zugewiesen, so wird......die Methode des dynamischen Typs (Ferrari) verwendet, wenn die entsprechende Methode als virtual definiert wurde Andernfalls wird die Methode des statischen Typs (ItalienischesAuto) verwendet Polymorphismus in C++ entspricht also Polymorphismus in Java, wenn: Mit Pointern gearbeitet wird Und wenn alle Methoden (die overridden werden) als virtual definiert werden Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 20 Erklärung: Bei der Zuweisung ia = &f werden nur Pointer kopiert, ia zeigt also auf den Anfang von f; und damit auf das gesamte Ferrari-Objekt. Dadurch ist Polymorphismus prinzipiell möglich. Werden Spezifikation und Implementierung der Klasse getrennt (was man ja eigentlich immer machen sollte), so wird das Keyword virtual nur bei der Spezifikation der Klasse, nicht aber bei der Implementierung angegeben. 20

Polymorphismus (5) Polymorphismus funktioniert natürlich auch auf dem Heap, wo automatisch mit Pointern gearbeitet wird: Ferrari *f = new Ferrari(); f->marke(); // Ferrari f->fahre250(); // 250! ItalienischesAuto *ia = f; ia->marke(); // Ferrari ia->fahre250(); // geht nicht Weil Referenzen intern nichts anderes als Pointer sind, funktioniert Polymorphismus auch mit Referenzen: Ferrari f; f.marke(); // Ferrari f.fahre250(); // 250! ItalienischesAuto &ia = f; ia.marke(); // Ferrari ia.fahre250(); // geht nicht Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 21 Auch hier wird in beiden Fällen bei der Zuweisung ia = f nur ein Pointer kopiert. Obwohl Polymorphismus mit Referenzen funktioniert, sind Referenzen nicht geeignet, um wirklich generell mit Polymorphismus zu arbeiten. Das Problem liegt daran, dass eine Referenz gleich bei der Deklaration ein Objekt erhalten muss, welches es referenziert und dann immer nur das Objekt referenzieren kann, mit welchem es initialisiert wurde. Im Beispiel oben kann also ia nie ein anderes Objekt als f referenzieren wie zb ein Objekt vom Typ Fiat oder vom Typ einer Subklasse von Ferrari. Auch gibt es in C++ keine Arrays of References. Pointer haben diese Einschränkungen nicht, sind dadurch flexibler und mächtiger und drängen sich deshalb bei der Arbeit mit Polymorphismus meist auf. Im Allgemeinen sind Referenzen sehr praktisch bei der Parameterübergabe an Funktionen und methoden, weil die Schreibweise dadurch im Vergleich zu Pointern einfacher wird. Dabei funktioniert Polymorphismus natürlich auch: ist der Parameter vom Typ einer Referenz auf ItalienischesAuto und wird beim Aufruf ein Ferrari übergeben, dann wird sich innerhalb der Funktion/Methode die Referenz genauso verhalten wie ia im unteren Beispiel auf der obigen Folie. 21

Polymorphismus (6) Bei Polymorphismus spricht man auch oft von early binding und late binding Early binding: bereits während des Kompilierens weiss man, welche Methode ausgeführt werden wird (statischer Typ): ia.marke(); Late binding: erst zur Laufzeit kennt man den dynamischen Typ und dadurch die entsprechende Methode: ia->marke(); Eine Methode, die in einer Superklasse nicht virtual ist, kann in Subklassen nicht virtual gemacht werden Ebenso bleibt eine virtual Methode immer virtual, auch wenn in Subklassen das virtual Keyword nicht angegeben wird (in diesem Fall ist das Keyword virtual also optional) Wie in Java gibt es in C++ abstrakte Klassen (Auto in unserer Hierarchie) Können nicht instanziert werden Enthalten eine (oder mehrere) pure virtual Methoden (virtual, kein Body, dafür =0 am Ende) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 22 Zu early/late binding: Im ersten Fall sieht der Compiler, dass ia eine Objektvariable vom Typ ItalienischesAuto ist (kein Pointer, keine Referenz) und weiss damit, dass auf jeden Fall die Methode marke() von ItalienischesAuto aufgerufen wird. Der Methodenaufruf kann also fix in das ausführbare Programm eingefügt werden. Im zweiten Fall ist ia ein Pointer auf ein ItalienischesAuto (Referenz wäre identisch). Weil dieser Pointer aber auch auf einen Ferrari oder einen Fiat zeigen kann und die Methode marke() zudem virtual ist, kann erst zur Laufzeit anhand des dynamischen Typs entschieden werden, welche Methode zum Zug kommt. Der Aufwand für diese zusätzliche Entscheidung ist der Preis von Polymorphismus und resultiert während der Programmausführung in einem kleinen Performanceverlust. 22

Method Overriding Revisited (3) Erweitern wir ItalienischesAuto um eine Methode markefahre250: class ItalienischesAuto : public Auto { virtual void marke() {cout << "Macchina Italiana" << endl; void fahre250() {cout << "geht nicht" << endl; void markefahre250() {marke(); fahre250(); Testprogramm: Ferrari f; f.marke(); // Ferrari f.fahre250(); // 250! f.markefahre250(); // Ferrari geht nicht Wir beobachten also folgendes: Direkter Aufruf von marke und fahre250: Methoden von Ferrari werden ausgeführt Indirekter Aufruf (via markefahre250): Bei marke wird die Methode der Klasse Ferrari ausgeführt Bei fahre250 wird die Methode von ItalienischesAuto ausgeführt Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 23 Das folgende Programm verhält sich ebenfalls gleich wie das obige: Ferrari f; ItalienischesAuto *ia = &f; ia->markefahre250(); Dies bedeutet, dass via markefahre250() die virtual Methoden des dynamischen Typs (f) und nicht des statischen Typs (ia) ausgeführt werden. 23

Method Overriding Revisited (4) Erklärung: das liegt wiederum daran, ob die Methoden virtual sind oder nicht: markefahre250 ist eine Methode von ItalienischesAuto markefahre250 ruft deshalb grundsätzlich ebenfalls die Methoden marke und fahre250 von ItalienischesAuto auf fahre250 ist nicht virtual es wird immer die Methode von ItalienischesAuto aufgerufen, egal ob markefahre250 für eine Variable vom Typ ItalienischesAuto oder einer Subklasse (Ferrari) ausgeführt wird marke ist virtual es wird die Methode des Typs der Variablen (Ferrari) ausgeführt Auch hier verhält sich Java wie C++ wenn in C++ alle Methoden virtual sind Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 24 24

Übung 1 zu Vererbung und Polymorphismus #include <iostream> // monster.cpp using namespace std; class Monster { protected: virtual void augen() {cout << ":"; virtual void nase () {cout << '-'; virtual void mund () {cout << ' '; void zeige() { augen(); nase(); mund(); cout << endl; class Beisser : public Monster { virtual void mund() {cout << 'X'; virtual void beiss() { cout << "beiss, beiss" << endl; class Vampir : public Beisser { virtual void augen() {cout << "8"; virtual void mund() {cout << '#'; int main() { Monster m; m.zeige(); Beisser b; b.zeige(); Vampir v; v.zeige(); m = v; m.zeige(); b = m; b.zeige(); Monster* pm = new Monster; Vampir* pv = new Vampir; pm->zeige(); pm->beiss(); pv->zeige(); pv->beiss(); pm = pv; pm->zeige(); pm->beiss(); Was wird von diesem Programm ausgegeben? Zusatzfrage: Was verändert sich, wenn mund nicht virtual ist? Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 25 25

Downcasting Downcasting (auch Reverse Polymorphism genannt) bedeutet die Konvertierung einer polymorphen Variablen in ihren dynamischen Typ In Java gibt es einen Check zur Laufzeit, ob ein Objekt wirklich korrekt konvertiert wird: ItalienischesAuto ia = new Fiat(); Fiat ft = (Fiat) ia; // Korrekt Ferrari fe = (Ferrari) ia; // Generiert eine ClassCastException In C++ gibt es keinen solchen Check: ItalienischesAuto *ia = new Fiat(); Fiat *ft = (Fiat*) ia; // Korrekt Ferrari *fe = (Ferrari*) ia; // Verhalten nicht definiert In Java kann man mit instanceof den dynamischen Typ herausfinden: ItalienischesAuto ia = new Fiat(); if (ia instanceof Fiat) { Fiat ft = (Fiat) ia;... Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 26 Der Name Reverse Polymorphism kommt daher, weil die Zuweisung zu einer polymorphen Variable rückgängig gemacht wird, indem man den dynamischen Typ erhalten möchte. Downcasting kommt von daher, dass man den Datentyp in der Klassenhierarchie nach unten casted. Sowohl in Java als auch in C++ muss dabei der Typ, in welchen konvertiert wird, explizit angegeben werden. 26

Dynamic Cast C++: kein instanceof, dafür ein spezieller Konvertierungstyp, der Dynamic Cast Gehört zum Runtime Type Information (RTTI) System und ist als Template Function implementiert Der Dynamic Cast prüft die Gültigkeit der Konvertierung und gibt im Fehlerfall NULL zurück: ItalienischesAuto *ia = new Fiat(); Fiat *ft = dynamic_cast<fiat*>(ia); if (ft) {... // Cast gültig else {... // Cast nicht gültig (ft == NULL (= 0)) Ein Dynamic Cast funktioniert nur, wenn die entsprechende Variable polymorph ist, das heisst: Wenn ein Pointer auf das Objekt verwendet wird Wenn die Klasse mindestens eine virtuelle Methode enthält Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 27 Template Functions werden im nächsten Kapitel behandelt. Zusatzinfo für extrem Interessierte: Nur wenn mindestens eine virtual Methode definiert wird, wird für das Objekt eine sogenannte Runtime Type Information generiert. Dies ist nötig, damit Polymorphismus für das Objekt überhaupt funktioniert, denn dadurch kann der Typ eines Objekts zur Laufzeit rausgefunden werden und dadurch virtual Methoden in Abhängigkeit vom dynamischen Typ der Variablen richtig aufgelöst werden. Der dynamic cast verwendet genau diese Runtime Type Information, um den dynamischen Typ zu bestimmen und existiert diese Information nicht, dann funktioniert dies eben nicht. Durch die Konsultation der Runtype Time Information ist der Dynamic Cast performancemässig eine relativ teure Operation und sollte deshalb nur dann durchgeführt werden, wenn man zur Laufzeit den dynamischen Typ der polymorphen Variablen nicht kennt. Falls man diesen eindeutig kennt, so genügt ein normaler Cast oder ein Static Cast (siehe nächste Folie). 27

Static Cast Es gibt auch einen Static Cast; hat ähnliche Syntax wie Dynamic Cast; hat sonst aber wenig damit zu tun Sehr ähnlich wie ein normaler Cast wie zb (Ferrari*) oder (int) Der Static Cast prüft jedoch den statischen Typ eine potentiell unsinnige Konvertierung von int* zu int wird verhindert Ist damit etwas sicherer als der normale Cast Beispiel für normale Casts und Static Casts: void* v =...; // Wir wissen von irgendwoher, dass v auf einen Ferrari zeigt Ferrari *fe1 = (Ferrari*) (v); // Normaler Cast Ferrari *fe2 = static_cast<ferrari*>(v); // Static Cast int *pi = new int; *pi = 12; int b = (int) pi; // Normaler Cast int* int: OK int c = static_cast<int>(pi); // Static Cast int* int: // Kompilierfehler double d = 3.14; int e = static_cast<int>(d); // Static Cast double int OK Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 28 Der Static Cast steht gleich hier nach dem Dynamic Cast weil der Gebrauch sehr ähnlich ist; die beiden Casts haben aber sonst wenig miteinander zu tun: Der Dynamic Cast dient dazu, eine sichere Konvertierung einer polymorphen Variable in ihren dynamischen Typ durchzuführen. Der Static Cast ist einfach eine etwas restriktivere (und damit potentiell sicherere) Variante als der normale Cast, da er unsinnige Casts vermeidet und jeweils einen Kompilierfehler generiert. Mit Polymorphismus hat der Static Cast aber nichts zu tun. 28

Method Overloading in Subklassen (1) Java & C++: Eine Methode einer Superklasse kann in einer Subklasse überladen werden Java: Eine Instanz der Subklasse kann die Methoden der Superklasse und die überladenen Methoden in der Subklasse einfach verwenden: // Overloading.java class Parent { public void test() {System.out.println("test()"); class Child extends Parent { public void test(int i) {System.out.println("test(int)"); public class Overloading { public static void main(string[] args) { Child c = new Child(); c.test(1); // Gibt test(int) aus c.test(); // Gibt test() aus Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 29 29

Method Overloading in Subklassen (2) Das gleiche Beispiel in C++: // overloading.cpp class Parent { void test () {cout << "test()" << endl; class Child : public Parent { void test(int i) {cout << "test(int)" << endl; int main() { Child c; c.test(1); c.test(); Beim Kompilieren mit g++ resultiert folgender Output: overloading.cpp: In function 'int main()': overloading.cpp:17: error: no matching function for call to 'Child::test()' overloading.cpp:11: error: candidates are: void Child::test(int) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 30 30

Method Overloading in Subklassen (3) Grund für den Kompilierfehler: C++ bei der Namensauflösung (welche Methode gehört zu welchem Methodenaufruf) wie folgt vor: 1. Suche die erste Klasse (beginnend mit der eigenen Klasse), in welcher der Name der aufgerufenen Methode auftritt test wird in der Klasse Child gefunden 2. Suche die Methode innerhalb dieser Klasse Child kennt test(int), nicht aber test() Kompilierfehler Abhilfe: Jede in Subklassen überladene Methode sollte dort neu definiert werden (am einfachsten mit einer inline Methode) class Child : public Parent { void test() {Parent::test(); void test(int i) {cout << "test(int)" << endl; Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 31 Man kann sich jetzt fragen, wieso es in Java keinen Fehler gab. Dies hat mit der Namensauflösung in Java zu tun: Es gibt nur einen Schritt, bei welchem die komplette Signatur der Methode zur Suche verwendet wird und nicht nur deren Namen. Damit wird die richtige Klasse immer gefunden wenn die Methode wirklich existiert. 31

Virtual Destructors (1) Im Zusammenhang mit Polymorphismus stellt sich die Frage, ob ein Destructor virtual deklariert werden soll Erweiterung unserer Hierarchie mit Destructors: class Auto { virtual ~Auto() {cout << "Bye bye Auto" << endl;... public class ItalienischesAuto extends Auto { ~ItalienischesAuto() {cout << "Addio Italienisches Auto" << endl;... class Ferrari : public ItalienischesAuto { ~Ferrari() {cout << "Ciao Ferrari" << endl; Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 32 Wie bei Method Overriding im Zusammenhanh mit normalen Methoden ist es auch hier entscheidend, ob an oberster Stelle der Destructor virtual definiert ist. Ebenfalls kann das virtual Keyword in den Destructors der Subklassen weggelassen werden. 32

Virtual Destructors (2) Testprogramm: Auto *a = new Ferrari(); delete a; // Ausgabe: Ciao Ferrari Addio Italienisches Auto Bye bye Auto ItalienischesAuto *ia = new Ferrari(); delete ia; // Ausgabe: Ciao Ferrari Addio Italienisches Auto Bye bye Auto Also gilt: Hat der statische Typ (Auto, Italienisches Auto) einen virtuellen Destructor, so werden alle Destructors von unten nach oben ab dem dynamischen Typ (Ferrari) ausgeführt Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 33 Liegen Objekte auf dem Stack, so werden beim Freigeben des Speichers (zb beim Verlassen einer Methode/Funktion) immer alle Destructors, von unten (der Klasse des Objekts) nach oben (zur obersten Superklasse) ausgeführt, weil dann Polymorphismus und damit auch das Schlüsselwort virtual kein Rolle spielt. 33

Virtual Destructors (3) Ist der Destructor der Klasse Auto nicht virtual, werden von unten nach oben nur die Destructors ab dem statischen Typ ausgeführt: Auto *a = new Ferrari(); delete a; // Ausgabe: Bye bye Auto ItalienischesAuto *ia = new Ferrari(); delete ia; // Ausgabe: Addio Italienisches Auto Bye bye Auto Hat der statische Typ (und auch alle Superklassen davon) keinen Destructor, dann wird gar kein Destructor ausgeführt: Auto *a = new Ferrari(); // Auto hat keinen Destructor delete a; // Keine Ausgabe Rule of Thumb: Ein Destructor sollte dann virtual deklariert werden, wenn es mindestens eine andere virtuelle Methode hat Auch dann, wenn der Destructor nichts tut Sonst besteht die Gefahr, dass Destructors von Subklassen nicht ausgeführt werden Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 34 Der Grund für die Rule of Thumb liegt daran, dass Objekte einer Klasse (und ihrer Subklassen) prinzipiell polymorph verwendet werden können, wenn mindestens eine Methode virtual ist. In diesen Fällen sollte man den Destructor nicht nicht polymorph machen, indem man keinen virtuellen Destructor zur Verfügung stellt. In unserem Beispiel sollte man also in der Klasse Auto also einen leeren, virtuellen Destructor einfügen, denn die Klasse hat eine virtuelle Methode Auto. Dieser leere virtuelle Destructor muss nur in der obersten Klasse mit virtuellen Methoden eingefügt werden, eine weitere Programmierung eines leeren virtuellen Destructors in der Klasse ItalienischesAuto ist also nicht nötig, denn das polymorphe Verhalten des Destructors ist durch das Einfügen des virtuellen Destructors in der Klasse Auto garantiert. In Java verhalten sich Destructors (die finalize() Methode) immer wie virtual Destructors in C++. 34

Operator Overloading und Vererbung (1) Auch vom Programmierer überladene Operatoren werden vererbt Klasse rect (Operator Overloading Kapitel) Klasse quader: class rect { // quader.cpp int x, int y; rect (): x(0), y(0) { rect & operator ++ () { // Prefix ++ Operator ++x; ++y; return *this; ostream & operator << (ostream & os, const rect & r) { return os << r.x << " * " << r.y << " = " << r.x * r.y;... Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 35 35

Operator Overloading und Vererbung (2)... class quader : public rect { int z; quader () :z(0) { // Constructor Chaining, ruft rect() auf Testprogramm: quader c1; ++c1; // x und y (von rect) werden inkrementiert, nicht aber z cout << c1 << endl; // Ausgabe: 1 * 1 = 1 quader erbt also die Operatoren von rect, dahinter steckt folgendes: Der ++ Operator ist einfach eine normale Methode und wird darum vererbt Der (einzige) Parameter (rect) ist eine Superklasse von quader Darum funktioniert die Methode auch mit quader Der << Operator ist als Funktion implementiert und damit nicht Teil einer Klasse Womit die Funktion auch nicht vererbt wird quader kann die Funktion verwenden weil wiederum quader eine Subklasse von rect ist Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 36 36

Operator Overloading und Vererbung (3) Für die gewünschte Funktionalität für quader kann man die Operatoren als Methode neu implementieren oder eine weitere Funktion zufügen: class quader : public rect {... quader & operator ++ () { ++x; ++y; ++z; return *this; ostream & operator << (ostream & os, const quader & c) { return os << c.x << " * " << c.y << " * " << c.z << " = " << c.x * c.y * c.z; Testprogramm: quader c1; ++c1; // x, y und z werden inkrementiert quader << c1 << endl; // Ausgabe: 1 * 1 * 1 = 1 Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 37 Man beachte: Der << Operator ist nun zweimal als Funktion definiert, einmal für rect und einmal für quader. Weil die Funktionen aber verschiedenen Signaturen haben, ist die kein Problem. Polymorphismus gilt übrigens auch für überladene Operatoren, die als Methode implementiert sind. Wie bei normalen Methoden müssen diese aber als virtual deklariert werden, damit Polymorphismus überhaupt funktionieren kann. Dazu müsste als der ++ Operator in der Klasse rect wie folgt implementiert werden: virtual rect & operator ++ () { ++x; ++y; return *this; Mit Operatoren, die als Funktion ausserhalb von Klassen überladen sind, funktioniert Polymorphismus nicht. 37

Übung 2 zu Vererbung und Polymorphismus class Person { char nachname[20]; protected: Person* partner; Person (const char* n) { strcpy(nachname, n); partner = NULL; void heirat (Person* p) {partner = p; const char* name() {return nachname; class Mann : public Person { Mann(const char* n) : Person(n) { class Frau : public Person { Frau(const char* n) : Person(n) { const char* maedchenname() { return nachname; int main() { Person *m = new Mann("Meier"); Person *f = new Frau("Frei"); cout << m->name() << endl; cout << f->name() << endl; m->heirat(f); cout << m->name() << endl; cout << f->name() << endl; 1. Fügen Sie die vom Programm benötigten #includes ein 2. Das Programm erzeugt trotzdem immer noch einen Kompilierfehler wieso? Korrigieren Sie den Fehler. 3. heirat(person*) soll geändert werden, dass beide Personen in einem Aufruf miteinander verheiratet werden 4. name() soll bei lediger Frau den Mädchennamen ausgeben, sonst den Familiennamen (Name des Ehemannes) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 38 38

Mehrfachvererbung (1) C++: Eine Klasse kann von mehreren Superklassen abgeleitet werden Mehrfachvererbung Wenn möglich sollte Mehrfachvererbung vermieden werden (fehleranfällig, verwirrend), dennoch kann es in einigen Fällen nützlich sein... und ein C++ Programmierer sollte zumindest die Grundidee verstanden haben Die Subklasse erbt die Eigenschaften aller Superklassen Prinzipiell ist Mehrfachvererbung auch ziemlich problemlos, schwieriger wird es aber, wenn folgendes auftritt: Zwei oder mehr Superklassen haben eine Methode mit der gleichen Signatur oder eine Instanzvariable mit dem gleichen Namen welche soll jetzt in der Subklasse verwendet werden? Zwei oder mehr Superklassen haben wiederum die gleiche Superklasse, ist diese Klasse (und deren Eigenschaften) jetzt doppelt vorhanden? a() A B a() Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 39?? A C Java kennt keine Mehrfachvererbung, dafür Interfaces. Mit Interfaces kann man fast alles machen wie mit Mehrfachvererbung und vermeidet gleichzeitig die meisten Probleme von Mehrfachvererbung. Der Grund liegt daran, dass Interfaces keine Instanzvariablen haben (womit eine Instanzvariable in einer Java Klasse nie mehrmals vorkommen kann) und dass Interfaces nur Methodenköpfe, aber nicht die Methoden selbst definieren (womit eine Methode nicht doppelt vorhanden sein kann). 39

Mehrfachvererbung (2) Beispiel mit Klasse Werwolf, die von Monster und Wolf abstammt: Monster Werwolf Wolf class Monster { // mehrfach1.cpp void erschrecken() {cout << "buuuuhh!" << endl; class Wolf { void heulen() {cout << "heuuull!" << endl; class Werwolf : public Monster, public Wolf { int main() { Werwolf ww; ww.erschrecken(); // buuuuhh! ww.heulen(); // heuuull! Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 40 40

Mehrfachvererbung (3) Erweiterung: Auch Wolf implementiert eine Methode erschrecken: // mehrfach2.cpp class Monster { void erschrecken() {cout << "buuuuhh!" << endl; class Wolf { void erschrecken() {cout << "fletsch!" << endl; void heulen() {cout << "heuuull!" << endl; class Werwolf : public Monster, public Wolf { int main() { Werwolf ww; ww.erschrecken(); // Kompilierfehler! ww.monster::erschrecken(); // buuuuhh! ww.wolf::erschrecken(); // fletsch! ww.heulen(); // heuuull! Werden mehrere Methoden (oder Instanzvariablen) mit der gleichen Signatur geerbt, so muss die Methode beim Aufruf eindeutig spezifiziert werden! (Klasse::) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 41 41

Mehrfachvererbung (4) Mehrfachvererbung mit gemeinsamer Basisklasse: class Monster { // mehrfach3.cpp protected: int opfer; Monster() : opfer(0) { void erschrecken() {cout << "buuuuhh!" << endl; class Beisser : public Monster { void friss() {cout << ++opfer << " Opfer!" << endl; class Zombie : public Monster { void friss() {cout << ++opfer << " Opfer!" << endl; class Vampir : public Beisser, public Zombie { int main() { Vampir dracula; dracula.friss(); // Kompilierfehler! dracula.erschrecken(); // Kompilierfehler! dracula.monster::erschrecken(); // Kompilierfehler! dracula.beisser::erschrecken(); // buuuuhh! dracula.zombie::erschrecken(); // buuuuhh! dracula.beisser::friss(); // 1 Opfer! dracula.zombie::friss(); // 1 Opfer! Monster Beisser Vampir Monster Zombie Auch hier gilt: Methoden (oder Instanzvariablen) müssen eindeutig spezifiziert werden! (Klasse::) Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 42 Die beiden letzten Zeilen geben jeweils 1 Opfer aus, da dracula einmal über den Beisser-Pfad und einmal über den Zombie-Pfad frisst. Weil in dracula Monster doppelt vorhanden ist, ist auch die Instanzvariable opfer doppelt vorhanden. 42

Mehrfachvererbung (5) Virtuelle Mehrfachvererbung bewirkt, dass eine gemeinsame Basisklasse nur einmal vererbt wird Dazu wird das Keyword virtual verwendet Hat nichts mit dem virtual bei virtual Methoden zu tun ( shared wäre hier wohl besser gewesen) Dadurch ist eine genauere Bezeichnung der verwendeten Methoden und Instanzvariablen der gemeinsamen Basisklasse Monster nicht mehr nötig Beisser Monster Vampir Zugriffe auf Methoden mit gleichen Signaturen oder Instanzvariablen mit dem gleichen Namen in den Klassen Beisser und Zombie müssen aber immer noch eindeutig spezifiziert werden! Auf Einfachvererbung oder Mehrfachvererbung ohne gemeinsame Basisklasse hat das Keyword virtual keinen Einfluss Zombie Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 43 43

Mehrfachvererbung (6) Monster Beisser Zombie Vampir mit virtual Vererbung: Monster class Monster { // mehrfach4.cpp protected: int opfer; Monster() : opfer(0) { void erschrecken() {cout << "buuuuhh!" << endl; class Beisser : public virtual Monster { void friss() {cout << ++opfer << " Opfer!" << endl; class Zombie : public virtual Monster { void friss() {cout << ++opfer << " Opfer!" << endl; class Vampir : public Beisser, public Zombie { Beisser int main() { Vampir dracula; dracula.friss(); // Kompilierfehler! dracula.erschrecken(); // buuuuhh! dracula.monster::erschrecken(); // buuuuhh!; Monster optional dracula.beisser::erschrecken(); // buuuuhh!; Beisser optional dracula.zombie::erschrecken(); // buuuuhh!; Zombie optional dracula.beisser::friss(); // 1 Opfer!; Beisser notwendig dracula.zombie::friss(); // 2 Opfer!; Zombie notwendig Vampir Zombie Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 44 Weil dracula nun nur noch einmal die Eigenschaften von Monster erbt, ist auch die Instanzvariable opfer nur einmal vorhanden und die friss()-methode von Beisser oder Zombie verwendet immer diese Instanzvariable, unabhängig davon, ob dracula über den Beisser-Pfad oder den Zombie- Pfad frisst. Deshalb wird nach dem zweiten Fressen 2 Opfer ausgegeben. 44

Zusammenfassung In C++ gibt es public, protected und private Vererbung, womit die Sichtbarkeit von Instanzvariablen und Methoden in Subklassen eingeschränkt werden kann Bei Method Overriding implementiert eine Subklasse eine Methode einer Superklasse neu (gleiche Signatur ist notwendig); dadurch ist die alte Methode in der Subklasse nicht mehr sichtbar Polymorphismus bedeutet, dass eine Variable vom Typ einer Superklasse irgendeine Variable vom Typ einer Subklasse beinhalten kann Werden einander direkt Objekte (und nicht Pointer) zugewiesen, so werden immer die Methoden des statischen Typs der Variablen verwendet Werden einander Pointer auf Objekte zugewiesen, so werden die Methoden des dynamischen Typs verwendet, wenn die entsprechende Methode als virtual definiert wurde Mit einem Dynamic Cast kann eine polymorphe Variable in ihren dynamischen Typ konvertiert werden Überladene Operatoren werden auch vererbt C++ unterstützt Mehrfachvererbung; nach Möglichkeit sollte diese aber wegen ihrer Fehleranfälligkeit und Unübersichtlichkeit vermieden werden Marc Rennhard, 22.04.2010, CPP_Vererbung.ppt 45 45