Expliziter Konstruktoraufruf der Basisklasse notwendig?



  • Hi zusammen,

    der Code den ich jetzt hier hinpacke ist vielleicht ein bisschen lang für die kurzen Fragen, aber so ist es wohl übersichtlicher (man möge mir verzeihen):

    #include <iostream>
    #include <cmath>
    using namespace std;
    
    //Basisklasse
    class Figur 
    {  
      protected:
    		// Attribute:
        double x_MP;
        double y_MP;
        double z_MP;
        double flaeche;
      public:
    		// Methoden:
        Figur();
        Figur(double x, double y, double z);
        void setze_MP(double x, double y, double z);
        void print_MP();
        virtual void info()=0;
        virtual void berechne_flaeche()=0;
    };
    
    //Subklassen
    class Rechteck:public Figur
    {
       private:
    
          double laenge;
          double breite;
    
       public:
    
           Rechteck();
           Rechteck(double x, double y, double z, double l, double b);
           void setKanten(double laenge, double breite);
           virtual void info();
           virtual void berechne_flaeche();
    };
    
    class Kreis:public Figur 
    {
      private:
    
        double radius;
    
      public:
    
        Kreis();
        Kreis(double x, double y, double z, double r);
        void setze_radius(double r);
        virtual void berechne_flaeche();
        virtual void info();
    };
    
    //Konstruktoren Figur
    Figur::Figur()
    {
       x_MP=0.0;
       y_MP=0.0;
       z_MP=0.0;
       flaeche=0.0;
    }
    
    Figur::Figur(double x, double y, double z)
    {
       x_MP=x;
       y_MP=y;
       z_MP=z;
    }
    
    //Konstruktoren Rechteck
    Rechteck::Rechteck():Figur()
    {
       breite=0.0;
       laenge=0.0;
    }
    
    Rechteck::Rechteck(double x, double y, double z, double l, double b):Figur(x, y, z)
    {
       breite=b;
       laenge=l;
    }                       
    
    //Konstruktoren Kreis
    Kreis::Kreis():Figur()
    {
       radius=0.0;
    }
    
    Kreis::Kreis(double x, double y, double z, double r):Figur(x,y,z)
    {
       radius=r;
    }
    
    // M E T H O D E N D E F I N I T I O N E N
    
    // Methoden Figur
    void Figur::setze_MP(double x, double y, double z)
    {
    	x_MP = x;
    	y_MP = y;
    	z_MP = z;
    }
    void Figur::print_MP()
    {
    	cout << "Mittelpunkt:" << endl;
    	cout << "X : " << x_MP << endl;
    	cout << "Y : " << y_MP << endl;
    	cout << "Z : " << z_MP << endl;
    }
    void Figur::info()
    {
    	cout << endl << "Dies ist eine Figur" << endl;
    }
    void Figur::berechne_flaeche()
    {}
    
    //Methoden Rechteck
    void Rechteck::setKanten(double laenge, double breite)
    {
         this->laenge=laenge;
         this->breite=breite;
    }
    
    void Rechteck::berechne_flaeche()
    {
         this->flaeche=(this->laenge*this->breite);
    }
    
    void Rechteck::info()
    {
         cout << "Dies ist ein Rechteck" << endl;
         cout << "Flaeche: "<< this->flaeche << endl;
    }
    
    // Methoden Kreis
    void Kreis::setze_radius(double r)
    {
    	radius = r;
    }
    void Kreis::berechne_flaeche()
    {
    	const double pi = 2*asin(1);
      flaeche = pi * radius * radius;
    }
    void Kreis::info()
    {
    	cout << endl << "Dies ist ein Kreis" << endl;
    	cout << "Flaeche: " << flaeche << endl;
    }
    
    // H A U P T P R O G R A M M
    
    int main()
    {
    	// Zeigerfeld der Basisklasse anlegen
    	Figur* figurenfeld[3];
    
        // Objekte verschiedener Klassen instantiieren
    	Rechteck fig1;
    	Kreis kreis1, kreis2;
    
    	//Kantenlaengen des Rechtecks setzen
    	fig1.setKanten(2.0, 3.0);
    
    	// Radii der Kreise setzen
    	kreis1.setze_radius(1.0);
    	kreis2.setze_radius(3.0);
    
        // Feld belegen
    	figurenfeld[0] = &fig1;
    	figurenfeld[1] = &kreis1;
    	figurenfeld[2] = &kreis2;
    
        // Methoden in einer Schleife aufrufen
    	for (int i = 0; i < 3; i++)
    	{
    		cout << endl << "Figur Nr. " << i+1 << endl;
    		figurenfeld[i]->berechne_flaeche();
    		figurenfeld[i]->info();
    	}
      system ("pause");
      return 0;
    }
    

    Das sind Verständnisfragen:

    1. Frage:

    Im Hauptprogramm erschaffe ich ein Rechteckobjekt mit

    Rechteck fig1;
    

    Ist es überhaupt notwendig, den Konstruktor der Basisklasse Figur explizit aufzurufen (siehe Definition des Parameterlosen Rechteckkonstruktors

    Rechteck::Rechteck():Figur()
    {
       breite=0.0;
       laenge=0.0;
    }, al
    

    )

    um die Werte x_MP, y_MP, z_MP und flaeche auf 0 zu setzen oder ruft ein parameterloser Subklassen-Konstruktor auch immer automatisch den parameterlosen Basisklassenkonstruktor auf?

    Ich hab beide Versionen ausprobiert (hab noch die print_MP Methode in die Schleife gepackt), in beiden Fällen zeigt er mir nur Nullen an. Ist es so wie ich vermute?

    2.Frage

    Welche Alternative existiert zum expliziten Aufruf des Konstruktors der Basisklasse (diesmal meine ich die Konstruktoren die wirklich Parameter übergeben)?

    Ist der Konstruktor der Basisklasse in diesem Fall wirklich notwendig?

    Danke für eure Antworten

    Gruß

    blind.io



  • @1: Wenn du nicht selber angibst, daß ein bestimmter Basis-Konstruktor verwendet werden soll, nimmt der Compiler automatisch den Default-Ctor (also den ohne Parameter).

    @2: Wenn du wirklich einen bestimmten Basis-Konstruktor benötigst (oder die Basisklasse keinen erreichbaren Default-Ctor hat), mußt du ihn in der Initialisierungsliste angeben. In deinem Beispiel wäre es vielleicht noch möglich, die Werte nachträglich über setze_MP() an die Basisklasse zu übergeben*, aber eine allgemeine Alternative gibt es da nicht.

    * Damit will ich nicht sagen, daß der Ansatz besser wäre - er ist eher ungünstiger als der Weg über die Initialisierungsliste, weil aufwendiger. Und wo wir gerade dabei sind, du solltest Member auch über die Initialisierungsliste vorbelegen:

    //unnötig (Default-Initialisierung und anschließende Zuweisung)
    MyClass(whatever x)
    { m_x = x; }
    //besser (Wert wird direkt initialisiert)
    MyClass(whatever x) : m_x(x) {}
    

    (bei double's macht das noch keinen großen Unterschied, aber bei größeren Klassen wird sowas kritisch)



  • ich zitiere mal stroustrup:
    "Falls die Basisklasse einen Konstruktor hat, dann muß dieser aufgerufen werden"

    ich würde es mir also einfach angewöhnen, du machst definitiv nichts falsch und bist auf der sicheren seite.



  • Wenn du jetzt noch Initialisierungslisten verwendest, sparst du dir die Zusweisungen:

    //Konstruktoren Figur
    Figur::Figur()
    	: x_MP(0.0), y_MP(0.0), z_MP(0.0), flaeche(0.0)
    	{ /* hier ist nichts mehr zu tun. */ }
    
    // [...]
    
    //Konstruktoren Rechteck
    // [...]
    Rechteck::Rechteck(double x, double y, double z, double l, double b)
    	: Figur(x, y, z), breite(b), laenge(l)
    	{ /* hier ist nichts mehr zu tun. */ }
    

    Welche Vorteile das nun für das Laufzeitverhalten hat, kann ich dir mangels Wissen nicht sagen. Ich persönlich finde es einfach übersichtlicher.

    Grüße...

    Heiko

    EDIT: doppelte Verneinung entfernt



  • ConfusedGuy schrieb:

    ich zitiere mal stroustrup:
    "Falls die Basisklasse einen Konstruktor hat, dann muß dieser aufgerufen werden"

    ich würde es mir also einfach angewöhnen, du machst definitiv nichts falsch und bist auf der sicheren seite.

    Würde ich nicht machen.
    Ich lege ja auch nicht Standard-operator=() oder -CpyCtor an oder übernehme sonstige Aufgaben des Compilers.
    Ich glaube auch, dass Stroustrup das nicht so gemeint hat....

    Gruß,

    Simon2.



  • du sparst dir dabei ein temporäres objekt, welches erst erstellt werden muss um es dann an die membervariable zuzuweisen.

    bei int, char, float etc. ist das noch ziemlich egal. bei komplexeren und größeren objekten sieht es da schon anders aus.



  • bwbg schrieb:

    Welche Vorteile das nun für das Laufzeitverhalten hat, kann ich dir nicht mangels Wissen nicht sagen. Ich persönlich finde es einfach übersichtlicher.

    Bei Build-in's wie double geht der Geschwindigkeitsunterschied wohl gegen Null, wenn du mit echten Klassen arbeitest, fällt der Unterschied schon stärker auf:

    class MyClass
    {
      whatever m_x;
    public:
      MyClass(const whatever& x)
      //hier wird der Default-Ctor von m_x ausgeführt
      {
        m_x = x; // ruft den Zuweisungsoperator von m_x auf
      }
    
      MyClass(const whatever& x)
      : m_x(x)//initialisiert m_x per Copy-Ctor
      {
        // keine weiteren Zusatztätigkeiten erforderlich
      }
    };
    

    Bei einem Build-in ist der "Default-Ctor"* leer und Copy-Ctor und Zuweisungsoperator identisch, bei einer eigenen Klasse hat erstens der Default-Ctor einiges zu tun und zweitens ist die Zuweisung oft aufwendiger als die Copy-Initialisierung (weil sie noch die alten Daten des Objekts aufräumen muß).

    *Für Puristen: Ich weiß, daß Build-in Typen keine Konstruktoren haben. Aber für eine vereinfachte Erklärung kann man ihre Initialisierung durchaus als Ctor-Aufrufe betrachten.


  • Mod

    Simon2 schrieb:

    ConfusedGuy schrieb:

    ich zitiere mal stroustrup:
    "Falls die Basisklasse einen Konstruktor hat, dann muß dieser aufgerufen werden"

    ich würde es mir also einfach angewöhnen, du machst definitiv nichts falsch und bist auf der sicheren seite.

    Würde ich nicht machen.
    Ich lege ja auch nicht Standard-operator=() oder -CpyCtor an oder übernehme sonstige Aufgaben des Compilers.
    Ich glaube auch, dass Stroustrup das nicht so gemeint hat....

    Den hab ich grad nicht bei der Hand - und sicherlich findet man immer Fälle, wo es anders sein wird. Als Faustregel finde ich es aber gut - alle Member explizit zu initialisieren ist durchaus hilfreich, um bereits beim Überfliegen des Codes eine Plausibilitätskontrolle machen zu können. Ob 1) alle Member aufgezählt sind, 2) diese in der deklarierten Reihenfolge stehen (hier warnen Compiler normalerweise) 3) keine fehlerhaften Abhängigkeiten bei der Initialisierung bestehen; kann man schnell mit einem Blick erfassen. Und dann, wenn die Initialisierungsliste zu lang wird, liegt wahrscheinlich sowieso ein Designfehler vor: zu viele Member indizieren, dass die Klasse mehr als eine Aufgabe erfüllt.

    In diesem speziellen Fall hätte ich persönlich sogar auf den Defaultkonstruktor in der Basisklasse verzichtet (oder ihn ggf. durch Defaultargumente erzeugt) - ich habe intuitiv eine Vorstellung davon, was Figur(x,y,z) macht, wenn ich weiß, dass eine Figur ihre Position kennt - beim Defaultkonstruktor ist das nicht unbedingt so. Zudem führt diese Codeduplizität leicht zu Fehlern. Zum Beispiel auch hier: Flaeche wird im x,y,z-Konstruktor nicht initialisiert (naja, mit diesem Member habe ich sowieso ein Problem - das hat in der Basisklasse nichts verloren).



  • Simon2 schrieb:

    ConfusedGuy schrieb:

    ich zitiere mal stroustrup:
    "Falls die Basisklasse einen Konstruktor hat, dann muß dieser aufgerufen werden"

    ich würde es mir also einfach angewöhnen, du machst definitiv nichts falsch und bist auf der sicheren seite.

    Würde ich nicht machen.
    Ich lege ja auch nicht Standard-operator=() oder -CpyCtor an oder übernehme sonstige Aufgaben des Compilers.
    Ich glaube auch, dass Stroustrup das nicht so gemeint hat....

    Gruß,

    Simon2.

    naja, ich würde den tipaufwand für einen aufruf eines basisklassen c'tors nicht mit dem erstellen eines =() oder eines cpyctors vergleichen wollen. und ja das macht der compiler von selbst. ist ja auch richtig so.


Anmelden zum Antworten