Objektorientierte Programmierung C++ Beispiel Essay

Einleitung[Bearbeiten]

In diesem Abschnitt geht es um virtuelle Methoden. Dies sind Methoden, bei denen man erwartet, dass sie in abgeleiteten Klassen redefiniert werden. Der Hauptnutzen von virtuellen Methoden ist die korrekte Verwendung von gleichnamigen Mitgliedsmethoden in einer Vererbungshierarchie. So kann bewirkt werden, dass die Verwendung eines Basisklassenzeigers oder einer Basisklassenreferenz bei Aufruf die Methode am Ende der Hierarchie aufruft, ohne dass diese an der jeweiligen Stelle bekannt sein muss. So wird erreicht, dass das Objekt nicht in einen ungültigen Zustand gerät, wenn eine Instanz einer ggf. mehrfach abgeleiteten Klasse über ihre Basisklassenschnittstelle angesprochen wird.

Was bringen sie?[Bearbeiten]

Virtuelle Methoden ermöglichen es dem Übersetzer, die passendste Methode in der Klassenhierarchie zu finden. Wird auf dieses reservierte Wort verzichtet, so wird im Zweifelsfall immer die Methode mit der gleichen Signatur des Urahnen genommen.

Tipp

Virtuelle Methoden gibt es nicht in allen objektorientierten Sprachen. So entspricht sie in Java einer ganz normalen Methode, die nicht ist.

1 #include<iostream> 2 3 classTier{ 4 public: 5 virtualvoidiss(){std::cout<<"Fresse wie ein Tier"<<std::endl;}; 6 // void iss() { std::cout << "Fresse wie ein Tier" << std::endl; }; 7 }; 8 9 classHund:publicTier{10 public:11 voidiss(){std::cout<<"Wuff! Fresse gerade"<<std::endl;};12 };13 14 classMensch:publicTier{15 public:16 voidiss(){std::cout<<"Esse gerade"<<std::endl;};17 };18 19 #include<vector>20 21 intmain(){22 std::vector<Tier*>tiere;23 tiere.push_back(newTier());24 tiere.push_back(newHund());25 tiere.push_back(newMensch());26 27 for(std::vector<Tier*>::const_iteratorit=tiere.begin();it!=tiere.end();it++){28 (*it)->iss();29 delete*it;30 }31 32 return0;33 }
Ausgabe:
1 Fresse wie ein Tier 2 Wuff! Fresse gerade 3 Esse gerade

Würden wir im obigen Beispiel das entfernen, so hätten wir das Ergebnis

1 #include<iostream>2 3 classTier{4 public:5 voidiss(){std::cout<<"Fresse wie ein Tier"<<std::endl;};6 };7 8 [...]
Ausgabe:
1 Fresse wie ein Tier 2 Fresse wie ein Tier 3 Fresse wie ein Tier

Virtuelle Destruktoren[Bearbeiten]

Eine weitere Eigenheit von C++ sind Destruktoren, die für Tätigkeiten wie Speicherfreigabe verwendet werden. Jede Klasse, deren Attribute nicht primitive Typen sind oder die andere Ressourcen verwendet (wie z.B. eine Datenbankverbindung) sollten diese unbedingt in ihren Destruktoren freigeben. Um auf den richtigen Destruktor immer zugreifen zu können, muss der Destruktor des Urahnen mit dem Wort beginnen.

Folgendes Beispiel zeigt die Verwendung und Vererbung von nicht-virtuellen Destruktoren, was zu undefiniertem Verhalten führt.

1 #include<iostream> 2 3 classA{ 4 public: 5 A(){} 6 ~A(){std::cout<<"Zerstöre A"<<std::endl;} 7 }; 8 9 classB:publicA{10 public:11 B(){}12 ~B(){std::cout<<"Zerstöre B"<<std::endl;}13 };14 15 intmain()16 {17 A*b1=newB;18 B*b2=newB;19 20 deleteb1;// Gemäß C++-Standard undefiniertes Verhalten.21 // Meist wird nur ~A() aufgerufen, da ~A() nicht virtuell.22 deleteb2;// Destruktoren ~B() und ~A() werden aufgerufen23 24 return0;25 }
Ausgabe:
1 Zerstöre A 2 Zerstöre B 3 Zerstöre A

Einleitung[Bearbeiten]

Abstrakte Klassen sind Klassen:

  • die nur aus einer Deklaration bestehen können.
  • von denen niemals Objekte erstellt werden. Keine Instanziierung möglich.
  • die oft als sog. Schnittstellen in umfangreichen Anwendungen verwendet werden.
  • die oft als Startpunkt(e) einer Vererbungshierarchie gedacht sind.

Deklarieren[Bearbeiten]

Eine Klasse wird dadurch abstrakt, indem man eine ihrer Mitgliedsmethoden als rein virtuell deklariert. Dazu schreiben Sie =0 hinter die Deklaration einer virtuellen Methode.

1 classStatusAusgeber2 {3 public:4 virtualvoidprintStatus(void)=0;// Rein virtuelle Deklaration5 virtual~StatusAusgeber(){}6 };

Der Versuch, eine Instanz von dieser Klasse zu erstellen, schlägt fehl:

1 intmain(void)2 {3 StatusAusgeberinstanz;//Instanz von abstrakter Klasse kann nicht erstellt werden4 return0;5 };

Verwenden[Bearbeiten]

Nun beschreiben wir die Verwendung von abstrakten Klassen anhand eines Beispiels. Dazu verwenden wir die abstrakte Klasse StatusAusgeber aus dem vorigen Abschnitt.

An gewissen Stellen Ihrer Programme wollen Sie die Funktionalität einer Klasse sicherstellen und verwenden, ohne auf die Funktionalitäten abgeleiteter Klassen eingehen zu müssen.

Wir deklarieren die Klassen Drucker und Bildschirm.

1 classDrucker:publicStatusAusgeber 2 { 3 unsignedintm_nDruckeAusgefuehrt; 4 unsignedintm_nLuefterAnzahl; 5 // Diverse druckerspezifische Attribute 6 public: 7 // Diverse druckerspezifische Methoden 8 voidprintStatus(void) 9 {10 std::cout11 <<"Geraet: Drucker"12 <<std::endl13 <<"Drucke ausgefuehrt: "<<m_nDruckeAusgefuehrt14 <<std::endl15 <<"Verbaute Luefteranzahl: "<<m_nLuefterAnzahl16 <<std::endl;17 }18 };19 20 classBildschirm:publicStatusAusgeber21 {22 unsignedintm_nLeistungsaufnahmeWatt;23 unsignedintm_nDiagonaleAusdehnungZoll;24 // Diverse bildschirmspezifische Attribute25 public:26 // Diverse bildschirmspezifische Methoden27 voidprintStatus(void)28 {29 std::cout30 <<"Geraet: Bildschirm"31 <<std::endl32 <<"Leistungsaufnahme (Watt): "<<m_nLeistungsaufnahmeWatt33 <<std::endl34 <<"Bildschirmgroesse diagonal (Zoll): "<<m_nDiagonaleAusdehnungZoll35 <<std::endl;36 }37 };

Vorteil dieser Vorgehensweise ist die spätere Verwendung von Methoden der Basisklasse, bei denen die Implementierung erzwungen wurde. Der Verwender der abgeleiteten Klasse kann sich darauf verlassen, dass die Methode implementiert wurde, ohne die weiteren Teile der Hierarchie zu kennen.

Wir deklarieren die Klasse GeraeteMitStatusSpeicher

1 classGeraeteMitStatusSpeicher:std::vector<StatusAusgeber*> 2 { 3 staticconstchar*_Trennzeile;// Trennzeile zwischen den Statusangaben 4 public: 5 voidspeichern(StatusAusgeber*GeraetMitStatus) 6 { 7 this->push_back(GeraetMitStatus);// Gerätezeiger im Vektor speichern 8 } 9 voidprintStatus(void)10 {11 std::vector<StatusAusgeber*>::const_iteratorit=this->begin();12 while(it!=this->end())// Solange der Iterator nicht auf das Ende verweist13 {// Iteration über den gesamten Inhalt14 (*it)->printStatus();// Iterator dereferenzieren, enthaltenen Zeiger verwenden15 std::cout<<_Trennzeile<<std::endl;// Trennzeile ausgeben16 it++;// Nächsten möglichen Inhalt auswählen17 }18 }19 };20 constchar*GeraeteMitStatusSpeicher::_Trennzeile="---------------";// statisch in GeraeteMitStatusSpeicher

GeraetMitStatusSpeicher ist von abgeleitet, speichert Zeiger auf StatusAusgeber-Objekte.

Hinweis

Dies ist möglich, da Zeiger eine feste Größe haben. Speicherung von Objekten abstrakter Klassen ist hier nicht möglich, da an dieser Stelle unmöglich die Größe der effektiven Objekte zu erkennen ist, auf die diese Zeiger verweisen. Genau das war uns ja von vornherein klar, weil wir nur an der printStatus()-Methode interessiert sind, deren Existenz durch die abstrakte Basisklasse sichergestellt wird. Egal ob hier ein Drucker, Monitor oder irgendein anderes Objekt hineingerät, das von StatusAusgeber abgeleitet wurde, wir können den Status ausgeben.

  • speichern(StatusAusgeber *) speichert einen Zeiger auf ein StatusAusgeber-Objekt
  • printStatus() ruft die Methode printStatus() für alle gespeicherten Zeiger auf StatusAusgeber-Objekte auf

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *