Vererbung vs. Komposition



  • Hey zusammen,
    bei der Klausurvorbereitung hänge ich an folgender Aufgabe(die Klasse CPunkt ist gegeben):

    class CPunkt
    {
    public:
    CPunkt(double x, double y, double z);
    double getBetrag();
    void getCoordinates(double & x, double & y, double & z);
    private:
    double mx;
    double my;
    double mz;
    };
    

    a. Es sollen zwei Klassenvarianten CPunkt_Polar_V und CPunkt_Polar_K entworfen werden, die x,y,z-Parameter für einen kartesischen 3D-Punkt annehmen und zusätzlich in privaten Polar-Koordinaten speichern können. Die trigonometrischen Funktionen seien in cmath vorhanden.
    b. Auch diese Klassen haben eine getBetrag-Funktion, die o.g. Funktion wiederverwendet.
    c. Weiterhin sollen sie eine zusätzliche Funktion getCoordinates_Polar haben, um auch die 3 Po-lar-Koordinaten zurückzugeben.
    a. Es sollen zwei Klassenvarianten CPunkt_Polar_V und CPunkt_Polar_K entworfen werden, die x,y,z-Parameter für einen kartesischen 3D-Punkt annehmen und zusätzlich in privaten Polar-Koordinaten speichern können. Die trigonometrischen Funktionen seien in cmath vorhanden.
    b. Auch diese Klassen haben eine getBetrag-Funktion, die o.g. Funktion wiederverwendet.
    c. Weiterhin sollen sie eine zusätzliche Funktion getCoordinates_Polar haben, um auch die 3 Po-lar-Koordinaten zurückzugeben.

    Ich habe vorallem bei der Vererbung Verständnisprobleme...die Komposition habe ich wie folgt implementiert:

    class CPunkt_Polar_K
    {
    public:
    	CPunkt_Polar_K();
    	CPunkt_Polar_K(double fx, double fy, double fz);
    	void getBetrag();
    	void getCoordinates_Polar(double& Phi, double& Rho, double& r);
    
    private:
    	CPunkt Punkt_3D;
    	double Phi;
    	double Rho;
    	double r;
    	double mx;
    	double my;
    	double mz;
    };
    

    und hier die .cpp:

    #include "CPunkt_Polar_K.h"
    #include <math.h>
    
    CPunkt_Polar_K::CPunkt_Polar_K():Punkt_3D(0, 0, 0), Phi(0), Rho(0), r(0)
    {}
    
    CPunkt_Polar_K::CPunkt_Polar_K(double fx, double fy, double fz):Punkt_3D(fx, fy, fz), 
    																Phi(0), 
    																Rho(0), 
    																r(0),
    																mx(fx),
    																my(fy),
    																mz(fz),
    {}
    
    void CPunkt_Polar_K::getBetrag()
    {
    	Punkt_3D.getBetrag();
    }
    
    void CPunkt_Polar_K::getCoordinates_Polar(double& Phi, double& Rho, double& r)
    {
    	r = Punkt_3D.getBetrag();
    	Phi = atan2(my, mx);
    	Rho = acos(my / r);
    }
    

    Bei der Vererbungsklasse habe ich folgenden Ansatz:

    #pragma once
    #include "CPunkt.h"
    
    class CPunkt_Polar_V: public CPunkt
    {
    public:
    	CPunkt_Polar_V();
    	CPunkt_Polar_V(double fx, double fy, double fz);
    	void getBetrag();
    	void getCoordinates_Polar(double Phi, double Rho, double r);
    
    private:
    	double mx;
    	double my;
    	double mz;
    	double Phi;
    	double Rho;
    	double r;
    };
    

    Ich hoffe mir kann jemand helfen und erklären, wie ich hier die Vererbung implementiere und das generelle Prinzip dahinter...ich steig da noch nicht so ganz durch!

    Mit freundlichen Grüßen,
    Jan


  • Mod

    Deine erste Variante ist schon falsch. Nun, nicht direkt technisch falsch, aber von der Logik her. Daher: Erklär doch mal genau, was du dir zu jedem deiner Member denkst, wozu der da ist. Dann stell sicher:

    1. Ob diese Erklärung auch Sinn macht (Hinweis: es gibt bestimmt einen Grund warum ich speziell danach frage...)
    2. Ob das was du dir denkst auch wirklich überall so gemacht wird (Gleicher Hinweis wie bei 1.)

    Das wird dir dann auch helfen bei dem zweiten Teil deiner Aufgabe.



  • Ich habe gerade gemerkt, dass ich mich verlesen habe. Ich dachte, dass die Koordinaten des Punktes nochmal extra gespeichert werden sollen, was sich ja aber auf die Polarkoordinaten bezieht.

    Wäre es denn so richtig?

    #pragma once
    #include "CPunkt.h"
    
    class CPunkt_Polar_K
    {
    public:
    	CPunkt_Polar_K();
    	CPunkt_Polar_K(double fx, double fy, double fz);
    	void getBetrag();
    	void getCoordinates_Polar(double& Phi, double& Rho, double& r);
    
    private:
    	CPunkt Punkt_3D;
    	double Phi;
    	double Rho;
    };
    

    bzw.

    #include "CPunkt_Polar_K.h"
    #include <math.h>
    
    CPunkt_Polar_K::CPunkt_Polar_K():Punkt_3D(0, 0, 0), Phi(0), Rho(0), r(0)
    {}
    
    CPunkt_Polar_K::CPunkt_Polar_K(double fx, double fy, double fz):Punkt_3D(fx, fy, fz), 
    																Phi(0), 
    																Rho(0), 
    																r(0)
    													
    {}
    
    void CPunkt_Polar_K::getBetrag()
    {
    	Punkt_3D.getBetrag();
    }
    
    void CPunkt_Polar_K::getCoordinates_Polar(double& Phi, double& Rho, double& r)
    {
    	double x = 0;
    	double y = 0;
    	double z = 0;
    
    	Punkt_3D.getCoordinates(x, y, z);
    	r = Punkt_3D.getBetrag();
    	Phi = atan2(x, y);
    	Rho = acos(y / r);
    }
    

  • Mod

    Ich glaube, du hast die Aufgabe nicht verstanden. Oder so komisch halb, denn du erfüllst zufällige Teile der Aufgabenstellung, und gleichzeitig den selben Teil zur Hälfte nicht.

    Fangen wir ganz am Anfang bei den Membern deiner Klasse an: Was für einen Sinn macht das, dass Rho und Phi Membervariablen sind, gleichzeitig r aber nicht? Was denkst du, will die Aufgabenstellung diesbezüglich von dir?



  • Das r habe ich wohl aus versehen beim Kopieren rausgelöscht, sollte noch dazu. Die Parameter sollen später die kartesischen Koordinaten darstellen, und beim Aufruf der getCoordinates_Polar in die übergebenen Referenzparameter geschrieben werden.


  • Mod

    @jand61 sagte in Vererbung vs. Komposition:

    Die Parameter sollen später die kartesischen Koordinaten darstellen, und beim Aufruf der getCoordinates_Polar in die übergebenen Referenzparameter geschrieben werden.

    Bist du da sicher? Wozu sind sie Klassenvariablen, wenn sie bei dir in nur einer Funktion benutzt werden? Und noch nicht einmal das, denn wenn du denkst, dass r, Phi, und Rho in Zeile 26, 27, 28 das r, Phi, Rho deiner Klasse wären, dann irrst du dich. Das sind dort die Funktionsparameter.

    Daher sollte dir jetzt auffallen, dass deine Klasse 3 Member hat, die nirgends benutzt werden, aber von der Aufgabenstellung verlangt werden. Da stimmt also sicher etwas noch nicht 🙂



  • Jetzt habe ich glaube ich meinen Fehler gefunden...ich berechne bereits im Konstruktor mithilfe der übergebenen kartesischen Koordinaten die Polarkoordinaten, die dann in den entsprechenden Variablen gespeichert werden.
    In der get-Methode weiße ich dann die Funktionsparametern diesen Attributen zu.

    #include <math.h>
    
    CPunkt_Polar_K::CPunkt_Polar_K():Punkt_3D(0, 0, 0), Phi(0), Rho(0), r(0)
    {}
    
    CPunkt_Polar_K::CPunkt_Polar_K(double fx, double fy, double fz):Punkt_3D(fx, fy, fz)
    {
    	r = sqrt(pow(fx, 2) + pow(fx, 2));
    	Phi = atan2(fx, fy);
    	Rho = acos(fy / r);
    }
    
    void CPunkt_Polar_K::getBetrag()
    {
    	Punkt_3D.getBetrag();
    }
    
    void CPunkt_Polar_K::getCoordinates_Polar(double& pPhi, double& pRho, double& pr)
    {
    	pPhi = Phi;
    	pRho = Rho;
    	pr = r;	
    }
    


  • Ich wundere mich nur über die Überschrift des Threads. Ist das ein Teil der Aufgabenstellung? Falls ja, weshalb setzt Du das nicht um?

    P.S. Die Aufgabenstellung verlangt die Nutzung von #include <cmath> und nicht von #include <math.h>. Es ist nur eine Kleinigkeit, aber auch auf das sollte man achten.


  • Mod

    Das schaut schon logischer aus. Gut, nun zum nächsten Teil: Versuch den Versuch mit Vererbung doch noch einmal neu, mit dem Wissen, das du jetzt hast. Geht das nun besser?



  • Schonmal vielen Dank. Das hilft mir sehr!

    Bei der .h habe ich nun die getBetrag()-Methode weggelassen, da ich diese ja über die Kindklasse aufrufen kann(Richtig?)

    #pragma once
    #include "CPunkt.h"

    class CPunkt_Polar_V: public CPunkt
    {
    public:
    	CPunkt_Polar_V();
    	CPunkt_Polar_V(double x, double y, double z);
    	void getCoordinates_Polar(double& pPhi, double& pRho, double& pr);
    
    private:
    	double mx;
    	double my;
    	double mz;
    	double Phi;
    	double Rho;
    	double r;
    };
    

    Die.cpp habe ich jetzt wie folgt:

    #include "CPunkt_Polar_V.h"
    #include <math.h>
    
    CPunkt_Polar_V::CPunkt_Polar_V():mx(0), my(0), mz(0)
    {}
    
    CPunkt_Polar_V::CPunkt_Polar_V(double fx, double fy, double fz):mx(fx), my(fy), mz(fz)														
    {
    	r = sqrt(pow(fx, 2) + pow(fx, 2));
    	Phi = atan2(fx, fy);
    	Rho = acos(fy / r);
    }
    
    void CPunkt_Polar_V::getCoordinates_Polar(double& pPhi, double& pRho, double& pr)
    {
    	pPhi = Phi;
    	pRho = Rho;
    	pr = r;
    }
    

  • Mod

    Wofür sind mx, my, mz?

    Bei der .h habe ich nun die getBetrag()-Methode weggelassen, da ich diese ja über die Kindklasse aufrufen kann(Richtig?)

    Nun, probier's aus.

    Ich mache mal darauf aufmerksam, dass mathematisch gesehen dein r schon dem Betrag entspricht. Das kann man ausnutzen, muss man aber auch nicht.



  • @john-0 sagte in Vererbung vs. Komposition:

    P.S. Die Aufgabenstellung verlangt die Nutzung von #include <cmath> und nicht von #include <math.h>. Es ist nur eine Kleinigkeit, aber auch auf das sollte man achten.

    Solche Kleinigkeiten sind mir auch aufgefallen, denn bei zB acos(fx / r) suche ich unwillkürlich nach dem using namespace std. Eigentlich nimmt man eben <cmath> aus der STL.

    Und eine Sache, wo ich jetzt aber nicht weiß, ob es signifikant ist, beim einfachen Quadrieren würde ich statt std::pow(x, 2) lieber einfach(x * x) nehmen.



  • Ich habe jetzt die Klasse nochmal überarbeitet, und die Koordinaten entfernt, da ich ja den Punkt mit der Klasse CPunkt erzeuge. Nun habe ich das Problem, das in den beiden Konstruktoren die Fehlermeldung "Standardkonstruktor in der Klasse CPunkt nicht vorhanden" erscheint. CPunkt ist ja aber vorgegeben, d.h in dieser kann ich nix ändern. Hat da jemand einen Tipp?

    class CPunkt_Polar_V: public CPunkt
    {
    public:
    	CPunkt_Polar_V();
    	CPunkt_Polar_V(double x, double y, double z);
    	void getCoordinates_Polar(double& pPhi, double& pRho, double& pr);
    
    private:
    	double Phi;
    	double Rho;
    	double r;
    };
    
    #include "CPunkt_Polar_V.h"
    #include "CPunkt.h"
    #include <cmath>
    
    
    CPunkt_Polar_V::CPunkt_Polar_V() :Rho(0), Phi(0), r(0)
    {}
    
    CPunkt_Polar_V::CPunkt_Polar_V(double fx, double fy, double fz)
    {
    	r = sqrt(pow(fx, 2) + pow(fx, 2));
    	Phi = atan2(fx, fy);
    	Rho = acos(fy / r);
    }
    
    void CPunkt_Polar_V::getCoordinates_Polar(double& pPhi, double& pRho, double& pr)
    {
    	pPhi = Phi;
    	pRho = Rho;
    	pr = r;
    }
    


  • Du musst den Konstruktor halt aufrufen.

    CPunkt_Polar_V::CPunkt_Polar_V(double fx, double fy, double fz)
       : Punkt(fx, fy, fz)  // <---- hier!
    {
       ...
    }
    

    D.h. bei Polar-Punkt ist ja ein Punkt und somit musst du als erstes den Punkt-Konstruktor aufrufen.



  • Warum muss ich hier den Konstruktor aufrufen? Bei Vererbung bin ich noch nicht so fit...



  • @jand61 sagte in Vererbung vs. Komposition:

    Warum muss ich hier den Konstruktor aufrufen? Bei Vererbung bin ich noch nicht so fit...

    Es muss immer der Konstruktor der Basisklasse ausgeführt werden, entweder der Defaultkonstruktor (das wird implizit gemacht) oder ein ein angepasster wie in Deinem Fall.



  • okay...also habe ich dann durch den Konstruktoraufruf in der abgeleiteten Klasse Zugriff auf die Attribute der Basisklasse, könnte also mit diesen innerhalb der abgeleiteten Klasse arbeiten? Verstehe ich das richtig?
    Und in jedem Konstruktor(außer dem Standardkonstruktor) muss der jeweilige Konstruktor der Basisklasse explizit aufgerufen werden?



  • @jand61 sagte in Vererbung vs. Komposition:

    okay...also habe ich dann durch den Konstruktoraufruf in der abgeleiteten Klasse Zugriff auf die Attribute der Basisklasse, könnte also mit diesen innerhalb der abgeleiteten Klasse arbeiten? Verstehe ich das richtig?

    Nein, das hängt davon ab, wie Du die Member deklarierst. Es gibt public, private und protected. Letzteres erlaubt den Zugriff nur von abgeleiteten Klassen aus, bei private geht das nicht. Beim Konstruktur geht es darum, dass der geerbte Teil korrekt initialisiert wird.

    Beispiel wie man mit protected auf die Member der Basisklasse zugreifen kann bzw. dadurch die Notwendigkeit von Get Funktionen eliminiert.

    class Base1 {
    private:
        int x;
    public:
        virtual ~Base1() {};
        Base1 (int i) : x(i) {};
        virtual int getX () const {return x;}
    };
    
    class Derived1 : public Base1 {
    private:
        int y;
    public:
        Derived1 (int i1, int i2) : Base1 (i1), y (i2) {}
        int getY () const {return y;}
        virtual int compute () {
            return getX() + y;
        }
    };
    
    class Base2 {
    protected:
        int x;
    public:
        virtual ~Base2() {};
        Base2 (int i) : x(i) {}
    };
    
    class Derived2 : public Base2 {
    protected:
        int y;
    public:
        Derived2 (int i1, int i2) : Base2(i1), y(i2) {}
        virtual int compute () {
            return Base2::x + y;
        }
    };
    
    

    Und in jedem Konstruktor(außer dem Standardkonstruktor) muss der jeweilige Konstruktor der Basisklasse explizit aufgerufen werden?

    Sobald man die Basisklasse nicht über den Standardkonstruktor initialisieren will, muss man einen Konstruktor explizit in der abgeleiteten Klasse aufrufen.


  • Mod

    Du kannst dir Vererbung so vorstellen, dass die Kindklasse das gleiche wie die Elternklasse ist, plus die Sachen die in der Kindklasse stehen. Es gibt also einen Teil der Kindklasse, der die Elternklasse ist. Und dieser Teil muss natürlich auch korrekt initialisiert werden. Sonst wäre ja die Gesamtklasse nicht korrekt initialisiert.

    Ich finde die Aufgabenstellung in dieser Hinsicht etwas ungünstig. Es werden zwar die technischen Aspekte gelehrt, aber offensichtlich kam das tiefere Verständnis zu kurz, was Vererbung bedeutet.