Anfängerfrage zur Vererbeung



  • Hallo,

    ich weis nicht ob man so dumme Fragen stellen darf aber ich tue es einfach mal 🙂

    Sind die Elemente (Variablen), die man von der Basisklasse vererbt bekommt, ein und die selben, oder wird ein neues (gleiches) Element für die abgeleiteten Klassen erstellt?

    (Ich weis dass die Kon/Destruktoren verschieden sind)

    Ich möchte nämlich im Konstr. der Basisklasse ein Element initialisieren, und ich weis nicht ob ich diesen Wert in im Element der abgeleiteten Klasse habe oder ob es ein neues ist.

    Danke



  • Hallo

    Ja, die Elemente der Basis Klasse bleiben die ELemente der Basis Klasse.
    Unten ist ein Bsp. wie die Member der Basis Klasse initialisiert werden.

    class Base
    {
    protected:
        Base(int i) : m_i(i) {}
        ~Base() {}
    private:
        int m_i;
    };
    
    class Derived : public Base
    {
    public:
        Derived() : Base(27) {}
    };
    

    Ich hoffe das hilft.

    Simon



  • Edit: die noch angemerkten Kritikpunkte eingebaut um das ganze sauber zu machen.

    JD schrieb:

    ich weis nicht ob man so dumme Fragen stellen darf aber ich tue es einfach mal 🙂

    Sind die Elemente (Variablen), die man von der Basisklasse vererbt bekommt, ein und die selben, oder wird ein neues (gleiches) Element für die abgeleiteten Klassen erstellt?

    Die Frage deutet noch ein anderes Verständnisproblem an, und zwar hoffe ich das dir die Unterschiede zwischen Objekt (Instanz) und Klasse geläufig sind.

    Passen wir das Beispiel vom Vorredner an...

    class Base
    {
    public:
        Base(int i)
        : m_i(i)
        {}
    
        // Virtueller Destruktor (Destruktor sollte in der Regel bei Basisklassen
        // virtuell deklariert sein).
        virtual ~Base() {}
    
        void Set(int i)
        {
          m_i = i;
        }
        int Get() const
        {
          return m_i;
        }
    private:
        int m_i;
    };
    
    class Derived : public Base
    {
    public:
        Derived()
        : Base(27)
        {}
    };
    

    Grundsätlich ist JEDES Objekt einer Klasse eigenständig was seine Werte angeht...

    int main()
    {
      Base a(4); // <-- Ein Objekt a der Klasse Base (mit m_i = 4)
      Base b(8); // <-- Ein Objekt b der Klasse Base (mit m_i = 8)
      Derived c; // <-- Ein Objekt c der Klasse Derived (mit m_i = 27)
    }
    

    In allen Fällen besitzt JEDES Objekt einen eigenen Zustand, sprich die Variable m_i existiert effektiv 3mal (Den jedes Objekt hat seine Eigenen Variablen).

    In sofern ist die Antwort Nein. Aber dies ist auch vollkommen unabhängig ob du nur eine Klasse oder mehrere Verwendest. Die Abgeleitete Klasse besitzt aber auch eine Variable m_i, da sie die von der Basisklasse geerbt hat.

    Gleiche Membervariablen bekommst du Klassenübergreifend (und auch für die abgeleiteten Klassen) mit dem Schlüsselwort static hin. Wenn wir das Beispiel anpassen würden und m_i static wäre würde m_i in jeden der drei Objekte identisch sein (und am Schluß den wert 27 besitzen). Solange ein Member aber nicht static ist, ist dieser immer an das Objekt, nicht die Klasse gebunden.

    Aber es kann auch einfach sein das du die Frage schlecht formuliert hast.

    Machen wir aber einfach mal ein weiteres Beispiel:

    class Base
    {
      public:
        Base(int i)
        : m_i(i)
        {}
        virtual ~Base() {}
    
        void SetI(int i) { m_i = i; }
        int GetI() const { return m_i; }
        void SetSI(int i) { m_i = i; }
        int GetSI() const { return m_i; }
    
    private:
        int m_i; // Objektbezogen
        static int sm_i; // Klassenübergreifend
    };
    int Base::sm_i = 0; // Initialisierung der Static
    
    class Derived : public Base
    {
    public:
        Derived()
        : Base(27)
        {}
    };
    
    int main()
    {
      Base a(4); // <-- Ein Objekt a der Klasse Base (mit m_i = 4)
      Base b(8); // <-- Ein Objekt b der Klasse Base (mit m_i = 8)
    
      b.SetSI(12); // <-- Setzt die statische Variable sm_i auf 12
    
      Derived c; // <-- Ein Objekt c der Klasse Derived (mit m_i = 27)
    
      // Für a, b und c ist sm_i identisch (mit 12), m_i aber jedesmal
      // eigenständig
    }
    

    cu André



  • @asc

    Wenn Du den Destructor der Klasse Base public machst, dann muss er auch virtual sein. Er wird sonst nicht aufgerufen, wenn delete mit einem Base* aufgerufen wird.
    Und wenn ich noch anmerken darf... get funktionen sollten const sein.
    Gruss
    Simon



  • simon.gysi schrieb:

    Wenn Du den Destructor der Klasse Base public machst, dann muss er auch virtual sein.

    Nun erschlag den Neuling nicht gleich - in diesem Beispiel wird keine Polymorphie verwendet (und auch kein "delete mit einem Base*" aufgerufen), da ist es egal, ob die Klasse irgendwelche virtuellen Methoden besitzt.



  • @CStoll
    Ok. 🙂



  • simon.gysi schrieb:

    @asc
    Wenn Du den Destructor der Klasse Base public machst, dann muss er auch virtual sein... get funktionen sollten const sein.

    Stimmt, war nur so mit der Beschreibung vertieft das ich nur Beispiel kopiert und angepasst hatte... Hast aber Recht: Beispiele für Anfänger sollten möglichst korrekt sein.

    cu André



  • Erst mal vielen vielen Dank für die kompetenten Antworten!

    Noch eine Frage:

    Gibt es eine Möglichkeit dass ich aus Basisklassen auf Elemente der abgeleiteten zugreifen kann? Vielleicht als
    - Interface oder,
    - pointer-to-member(wobei dann die Elemente puclic sein müssten?)

    Die Sache ist die: Eigentlich könnte ich alles in eine Klasse packen und hätte gar keine Probleme. Habe aber mal gehört jede Klasse soll einen eigenen Sinn haben, und wenn doch mehrere dann lieber aufteilen, und genau das habe ich halt getan.



  • JD schrieb:

    Gibt es eine Möglichkeit dass ich aus Basisklassen auf Elemente der abgeleiteten zugreifen kann?

    Ja, es gibt Möglichkeiten. Man muss sich natürlich immer überlegen, ob es design-technisch sinnvoll ist. Hier ein Beispiel:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Tier
    {
    protected:
    	virtual string gibLaut() const = 0;
    
    public:
    	void lautpruefung() const
    	{
    		if (gibLaut() == "wau")
    		{
    			cout << "Hund!" << endl;
    		}
    		else if (gibLaut() == "oink")
    		{
    			cout << "Schwein!" << endl;
    		}
    	}
    };
    
    class Hund : public Tier
    {
    protected:
    	string gibLaut() const
    	{
    		return "wau";
    	}
    };
    
    class Schwein : public Tier
    {
    protected:
    	string gibLaut() const
    	{
    		return "oink";
    	}
    };
    
    int main()
    {
    	Hund h;
    	Schwein s;
    
    	h.lautpruefung();
    	s.lautpruefung();
    }
    


  • Ok habe es einfach so gemacht, dass ich den Wert den ich in der Methode der Basisklasse berechne per Referenz returne und diesen dann in der abgeleiteten einfach in das Element kopiere.
    Ein direkter Zugriff muss nicht unbedingt sein.

    Zwei andere Fragen zum Interface:

    1. Wieso müssen die Methoden der Klasse die die abstrakte Klasse implementiert auch virtuell sein? Mir leuchtet ein, dass die Methoden des Interfaces virtuell und = 0 gesetzt werden (um nicht implementiert werden zu müssen).

    2. Wozu ist das Interface gut? -Um die implementierende Klasse austauschen zu können, ohne dass in der zugreifenden was geändert werden muss?



  • @virtu: genau das ist meine Frage. Super das du einen Code bereitstellst.

    Was wenn es nun keine Hund oder Schwein Objekte gibt sondern ein Objekt Tier.
    Was wird dann aufgerufen? Ein "Wau" oder ein "Oink".

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Tier
    {
    protected:
        virtual string gibLaut() const = 0;
    
    public:
        void lautpruefung() const
        {
            if (gibLaut() == "wau")
            {
                cout << "Hund!" << endl;
            }
            else if (gibLaut() == "oink")
            {
                cout << "Schwein!" << endl;
            }
        }
    };
    
    class Hund : public Tier
    {
    protected:
        string gibLaut() const
        {
            return "wau";
        }
    };
    
    class Schwein : public Tier
    {
    protected:
        string gibLaut() const
        {
            return "oink";
        }
    };
    
    int main()
    {
        Tier t; //<-----------------
    
        t.lautpruefung();
    }
    


  • JD schrieb:

    @virtu: genau das ist meine Frage. Super das du einen Code bereitstellst.

    Was wenn es nun keine Hund oder Schwein Objekte gibt sondern ein Objekt Tier.
    Was wird dann aufgerufen? Ein "Wau" oder ein "Oink".

    Ist garnicht möglich da die Methode nicht implementiert wurde (virtual = 0) und daher keine Instanz von der Basisklasse erzeugt werden kann. Wenn es wirklich sinnvoll ist das es eine Standardbehandlung für die Methode gibt, kannst du diese Behandlung in der Basisklasse implementieren und das =0 entfernen...

    Aber dann müssen die Abgeleiteten Klassen die Methode auch nicht mehr zwangsweise implementieren, und in dem Fall wird die Standardbehandlung verwendet.

    cu André



  • Von der Klasse Tier darf man nicht direkt Objekte erstellen, da diese eine abstrakte Methode enthält. Man kann aber mit Zeigern oder Referenzen auf Objekte der Klasse Tier arbeiten. Wenn man einen Zeiger auf Tier durch dynamische Speicheranforderung (mit new) erstellt hat, muss man darauf achten, das die Klasse Tier einen virtuellen Destruktor enthält, damit beim Löschen auch der Destruktor der abgeleiteten Klasse aufgerufen wird.
    Beispiel:

    #include <iostream>
    #include <string>
    using namespace std;
    
    class Tier
    {
    protected:
    	virtual string gibLaut() const = 0;
    
    public:
    	virtual ~Tier()
    	{
    		//Ein virtueller Destruktor ist nötig, damit der Destruktor der abgeleiteten Klasse
    		//aufgerufen wird, obwohl ein Zeiger auf die Basisklasse benutzt wird.
    	}
    
    	void lautpruefung() const
    	{
    		if (gibLaut() == "wau")
    		{
    			cout << "Hund!" << endl;
    		}
    		else if (gibLaut() == "oink")
    		{
    			cout << "Schwein!" << endl;
    		}
    	}
    };
    
    class Hund : public Tier
    {
    protected:
    	string gibLaut() const
    	{
    		return "wau";
    	}
    };
    
    class Schwein : public Tier
    {
    protected:
    	string gibLaut() const
    	{
    		return "oink";
    	}
    };
    
    int main()
    {
    	Tier *t1 = new Hund;
    	Tier *t2 = new Schwein;
    
    	t1->lautpruefung();
    	delete t1; // Da der Tier-Destruktor virtuell ist, wird hier auch der Hund-Destruktor aufgerufen.
    
    	t2->lautpruefung();	
    	delete t2; // Da der Tier-Destruktor virtuell ist, wird hier auch der Schwein-Destruktor aufgerufen.
    }
    


  • @virtu:

    Fungiert dann diese abstrakte Klasse Tier als Interface?
    Oder was ist mit Interface gemeint.

    Weil habe oft Fälle gesehen, wo die implementierende Klasse auch virtuelle Funktionen hat, diese jedoch implementiert. also dann würde die class Hund so aussehen.

    Meine Frage: Warum dann die gibLaut() auch virtuell? Was hat das für einen Zweck?

    class Hund : public Tier
    {
    protected:
        virtual string gibLaut() const
        {
            return "wau";
        }
    };
    


  • Unter Interface versteht man üblicherweise eine Klasse die nur aus öffentlichen, abstrakten Methoden versteht und sonst keine anderen Member besitzt.

    Man benutzt "virtual", um Laufzeit-Polymorphie zu ermöglichen. Poymorphie bedeutet, dass über den gleichen Member-Namen (hier gibLaut) verschiedene Dinge getan werden. Wenn du bei Hund die Methode gibLaut virtual machst, ermöglichst Du für abgeleitete Klassen Laufzeit-Polymorphie. Wenn nicht, kannst du gibLaut zwar in einer abgeleiteten Klasse überschreiben, aber du hast keine Polymorphie über einen Zeiger auf ein Basisklassen-Objekt.

    // Die Klasse PitBull ist von Hund abgeleitet und implementiert gibLaut mittels Rückgabe von "WAU!!!"
    PitBull pb;
    pb.gibLaut(); //Ausgabe wie erwartet: WAU!!!
    
    Hund *h = new PitBull;
    h->gibLaut(); //Ausgabe abhängig davon, ob gibLaut in der Klasse Hund virtual ist.
    

    Dies ist insbesundere wichtig, wenn es Funktionen oder Methoden gibt, die mit allen Basisklassen-Objekten funktioneren sollen:

    void provoziereEinenHund(Hund *h);
    

    Damit in dieser Funktion, die gibLaut-Methode des PitBull aufgerufen wird, muss die Methode in Hund als virtual drinne sein.


Anmelden zum Antworten