Herzlich Willkommen, lieber Gast!
  Sie befinden sich hier:

  Forum » C / C++ (ANSI-Standard) » Vererbung - Anfängerfrage

Forum | Hilfe | Team | Links | Impressum | > Suche < | Mitglieder | Registrieren | Einloggen
  Quicklinks: MSDN-Online || STL || clib Reference Grundlagen || Literatur || E-Books || Zubehör || > F.A.Q. < || Downloads   

Autor Thread - Seiten: > 1 <
000
20.10.2015, 06:31 Uhr
Dummy



Hallo zusammen,

bin neu hier und möchte gerne eine Frage zur Vererbung stellen, die mir in den Lehrbüchern immer ganz logisch erscheint. Aber beim Versuch eigene Programme mit Vererbung zu entwickeln, stehe ich oft ratlos da, weil mir vieles nicht logisch erscheint. Was sicher darauf hinweist, das ich das Thema noch nicht wirklich verstanden habe.

Wird von einer Basisklasse geerbt, sollte eine "Ist-Ein" Beziehung vorliegen.
Ein oft verwendetes Verfahren ist der Zugriff auf abgeleitete Klassen über einen Zeiger der Basisklasse:

C++:
#include <iostream>
using namespace std;

class Tier
{
public:
    virtual void Fressen() {};
    virtual void Schwimmen() {};
    virtual void Fliegen() {};
};

class Goldbarsch : public Tier
{
public:
    void Fressen()
    {
        cout << "Goldbarsch frisst\n";
    }
    void Schwimmen()
    {
        cout << "Goldbarsch schwimmt\n";
    }
};

class Elster : public Tier
{
public:
    void Fressen()
    {
        cout << "Elster frisst\n";
    }
    void Fliegen()
    {
        cout << "Elster fliegt\n";
    }
};

class Spinne : public Tier
{
    void Fressen()
    {
        cout << "Spinne frisst\n";
    }
    void Netzspinnen()
    {
        cout << "Spinne spinnt Netz\n";
    }
};

class Raupe : public Tier
{
    void Fressen()
    {
        cout << "Raupe frisst\n";
    }
    void Metamorphose()
    {
        cout << "Raupe verwandelt sich in Schmetterling\n "
    }
};

void main()
{
    Tier* goldbarsch = new Goldbarsch();
    goldbarsch->Fressen();  // => "Goldbarsch frisst"
    goldbarsch->Fliegen();  // => ""
    goldbarsch->Netzspinnen(); // => "Error: "NetzSpinnen" is not a member of Tier"

    cin.get();
}


Goldbarsch, Elster, Spinne "ist ein" Tier - soweit verstanden.

Nun scheint es aber so, dass schon in der Basisklasse "Tier" alle möglichen Fähigkeiten (Methoden) der gesammten Tierwelt, z.B. Fliegen, Schwimmen, NetzSpinnen, Metamorphose usw..., definiert werden müssen, damit der Zugriff über einen Basisklassen Zeiger funktioniert?

Für mich wäre es logischer, wenn die Basisklasse nur ganz allgemeine Methoden zur Verfügung stellen würde, die alle Tiere gemeinsam haben, wie z.B. "Fressen", "Bewegen", "Fortpfanzen" etc. Und in den abgeleiteten Klassen würden zusätzliche Fähigkeiten (Methoden) definiert, wie "Fliegen", "Metamorphose", "NetzSpinnen" usw...

So muss die Basisklasse Tier, bei jedem weiteren abgeleiteten Tier das zusätzliche Fähigkeiten besitzt, auch selbst um diese Methoden erweitert werden. Sonst können ihre speziellen Fähigkeiten nicht über den Basisklassenzeiger aufgerufen werden.

Auch der Zugriff auf die abgeleiteten Klassen über den Basisklassenzeiger ist für mich nicht ganz schlüssig. So ist es möglich "goldbarsch->Fliegen()" aufzurufen, obwohl der Goldbarsch keine Methode (Fähigkeit) dafür besitzt.

Liegt da irgendwo ein Verständnisfehler bei mir vor? Hat die C++ Vererbung wirklich so viele Untiefen? Oder gibt es noch andere Vererbungsmuster die logischer und leichter zu verstehen sind?

Dieser Post wurde am 20.10.2015 um 07:13 Uhr von Dummy editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
001
21.10.2015, 10:47 Uhr
ao

(Operator)


Du hast es schon sehr gut verstanden, aber dein Modellansatz ist fehlerhaft. Die Klasse Tier hat virtuelle Methoden Schwimmen() und Fliegen(), das heißt, dass in deiner Welt jedes Tier schwimmen und fliegen kann. Das ist aber nicht der Fall, und damit kommt dein Modell in Konflikt mit der Wirklichkeit.


Zitat:
Für mich wäre es logischer, wenn die Basisklasse nur ganz allgemeine Methoden zur Verfügung stellen würde, die alle Tiere gemeinsam haben, wie z.B. "Fressen", "Bewegen", "Fortpfanzen" etc. Und in den abgeleiteten Klassen würden zusätzliche Fähigkeiten (Methoden) definiert, wie "Fliegen", "Metamorphose", "NetzSpinnen" usw...

Richtig. Also etwa so:


C++:
class Tier
{
public:
  virtual void Fressen () = 0;
  virtual void Bewegen () = 0;
};

class Goldfisch : public Tier
{
public:

  virtual void Fressen ()
  {
    std::cout << "Goldfisch frisst." << std::endl;
  }

  virtual void Bewegen ()
  {
    std::cout << "Goldfisch schwimmt." << std::endl;
  }
};

class Spinne : public Tier
{
public:
  virtual void Fressen()
  {
    std::cout << "Spinne frisst." << std::endl;
  }
  virtual void Bewegen()
  {
    std::cout << "Spinne krabbelt." << std::endl;
  }

  
  void NetzSpinnen ()
  {
    std::cout << "Spinne spinnt ein Netz." << std::endl;
  }

};



Beide Tierarten können fressen und sich bewegen. Dass sie sich auf unterschiedliche Weise bewegen (schwimmen bzw. krabbeln), nennt man im objektorientierten Sprech "Polymorphie": Die polymorphe Methode Bewegen hat in allen abgeleiteten Klassen dieselbe Signatur, sieht also für den Compiler immer gleich aus, tut aber was Unterschiedliches.


Zitat:
So muss die Basisklasse Tier, bei jedem weiteren abgeleiteten Tier das zusätzliche Fähigkeiten besitzt, auch selbst um diese Methoden erweitert werden. Sonst können ihre speziellen Fähigkeiten nicht über den Basisklassenzeiger aufgerufen werden.

Auch der Zugriff auf die abgeleiteten Klassen über den Basisklassenzeiger ist für mich nicht ganz schlüssig. So ist es möglich "goldbarsch->Fliegen()" aufzurufen, obwohl der Goldbarsch keine Methode (Fähigkeit) dafür besitzt.

Über den Basisklassenzeiger können (und sollen) nur Eigenschaften und Fähigkeiten aufgerufen werden, die allen Tieren gemeinsam sind. Fliegen gehört nicht dazu, deswegen hat es in der Basisklasse nichts verloren. Fliegen kann nur typsicher an der Klasse Elster aufgerufen werden.

Es ist nicht richtig, dass die Basisklasse die Vereinigungsmenge aller abgeleiteten Klassen ist. Die Basisklasse ist die Schnittmenge.

Wahrscheinlich wird man dieses Modell weiterentwickeln und folgendes bauen:

C++:
class Vogel : public Tier
{
public:
  virtual void Fliegen ();
};

class Elster : public Vogel {};
class Geier : public Vogel {};


Also eine weitere Basisklasse Vogel zwischen Tier und den einzelnen Vogelarten. Über einen Vogel*-Pointer kann man dann alle Vögel hochfliegen lassen, und die Fische und Spinnen bleiben am Boden.


Zitat:
Oder gibt es noch andere Vererbungsmuster die logischer und leichter zu verstehen sind?

Man muss drauf achten, dass man die Eigenschaften und Fähigkeiten der realen Objekte richtig modelliert. Das ist oft einfacher gesagt als getan, aber genau das ist das Faszinierende und Spannende am Softwareentwurf: Rausfinden, in welchen Beziehungen die Objekte stehen, mit denen man hantiert, und das richtig in einer Programmiersprache abbilden.

Wenn man merkt, man kann mit seinen Objekten komische Sachen machen (Fische fliegen lassen zum Beispiel), dann hat man wahrscheinlich einen Entwurfsfehler gemacht. Den sollte man umgehend korrigieren, sonst fällt einem das später auf die Füße.

Außer der Vererbung mit Klassen gibt es noch die Interface-Technik. Solltest du dir mal anlesen. In modernen Sprachen (Java, C#) gibt es dafür eigene Schlüsselwörter und auch etwas abweichende Regeln. C++ ist da leider ein bisschen veraltet, da werden Interfaces mit Hilfe abstrakter Basisklassen realisiert, was die Unterschiede etwas verwischt.

Dieser Post wurde am 21.10.2015 um 10:52 Uhr von ao editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
002
23.10.2015, 14:03 Uhr
Dummy



Vielen Dank für die ausführliche Antwort und die Beispiele!

Mich hat gerade die Grippe erwischt. Sorry, dass meine Antwort deshalb so spät kommt.

Zitat von ao:
Über den Basisklassenzeiger können (und sollen) nur Eigenschaften und Fähigkeiten aufgerufen werden, die allen Tieren gemeinsam sind. Fliegen gehört nicht dazu, deswegen hat es in der Basisklasse nichts verloren. Fliegen kann nur typsicher an der Klasse Elster aufgerufen werden.

Da lag wohl mein wesentlicher Verständnisfehler.

Meine Idee war über einen Basisklassenzeiger die verschiedenen Tierklassen (Elster, Spinne, Raupe..) einer Funktion zu übergeben. So wie mit einem Template beispielsweise (int, float oder double) einer Funktion übergeben werden können.

Kann eigentlich auch eine ganze Klasse als Template definiert werden?

Dieser Post wurde am 23.10.2015 um 14:05 Uhr von Dummy editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
003
01.11.2015, 02:26 Uhr
ao

(Operator)


Auch von mir Entschuldigung für die Verspätung, ich war eine Woche verreist.


Zitat von Dummy:
Meine Idee war über einen Basisklassenzeiger die verschiedenen Tierklassen (Elster, Spinne, Raupe..) einer Funktion zu übergeben. So wie mit einem Template beispielsweise (int, float oder double) einer Funktion übergeben werden können.


Erst mal sind Templates was völlig anderes als Vererbung. Nicht durcheinanderwerfen.

Natürlich kann man einen Basisklassen-Zeiger an eine Funktion übergeben. Diese Funktion kann dann aber nur das aufrufen, was durch die Basisklasse definiert wird, denn welches abgeleitete Objekt dahintersteckt, sieht sie ja nicht.

Sie kann mit einem dynamic_cast versuchen, die abgeleitete Klasse herauszufinden, aber eigentlich ist sowas Schweinkram. Eine Funktion, die z.B. Elster::Fliegen() aufrufen will, sollte in ihrem Interface nicht so tun, als könnte sie mit jedem Tier umgehen. Sie sollte sich besser direkt Elster-Pointer übergeben lassen.

Dieser Post wurde am 01.11.2015 um 02:28 Uhr von ao editiert.
 
Profil || Private Message || Suche Download || Zitatantwort || Editieren || Löschen || IP
Seiten: > 1 <     [ C / C++ (ANSI-Standard) ]  


ThWBoard 2.73 FloSoft-Edition
© by Paul Baecher & Felix Gonschorek (www.thwboard.de)

Anpassungen des Forums
© by Flo-Soft (www.flo-soft.de)

Sie sind Besucher: