Konstruktor und Virtuelle Methoden



  • Hallo an alle c++-Freunde.
    Bin neu hier im Forum und habe gleich ein paar Fragen auf die ich eine Antwort hier im Forum suche.
    Folgender Bsp.Code:

    class Dreieck : public Figur
    {
       protected:
       float g, h;
       public:
       Dreieck(){}
       Dreieck(float grundseite, float hoehe) : g(grundseite), h(hoehe) {}
       virtual void Daten();
       virtual void Flaeche();
    };
    
    class gleichseitigesDreieck : public Dreieck
    {
       float a;
       public:
       gleichseitigesDreieck(float seite) : a(seite) {}
       void Daten();
       void Flaeche();
    };
    

    Nun die Fragen:
    1. Wieso wird unbedingt der Default Konstruktor in der Klasse Dreieck benötigt?
    Bei weglassen gibt es compilerfehler.
    Wenn ich jedoch den Default Konstruktor in der Klasse Dreieck lasse, dann kann ich auch nicht initialisierte Objekte vom Typ Dreieck erstellen, dass will ich aber unterbinden, deswegen der zweite Konstruktor. Ist es anderweitig möglich das Problem zu umgehen?
    2. Die Klasse Figur ist eine Abstrakte Klasse mit den beiden Virtuellen Methoden Flaeche() und Daten(), macht es Sinn die beiden Methoden in der Basisklasse Dreieck auch virtuell anzulegen? Wenn ja welche Vor/Nachteile hat man dann davon.

    MfG
    Dima



  • Hallo

    1. Der Standardkonstruktor wird normalerweise nicht benötigt. Wenn bei dir Fehlermeldungen kommen dann liegt das an der Anwendung der Klasse die den Standardkonstruktor erfordert, zum Beispiel std::vector<Dreieck>. Da vector eventuell gezwungen ist die Instanzen zu kopieren braucht deine Klasse entweder einen Standardkonstruktor oder einen Kopierkonstruktor. Mit letzterem kannst du das Problem der nicht initialisierten Instanzen umgehen.

    2. Es macht keinen Unterschied. Du kannst in Dreieck das virtual weglassen.

    bis bald
    akari



  • Hallo,

    Der Standardkonstruktor wird bei beim Konstruktor von gleichseitigesDreieck aufgerufen und deshalb benötigt. Wenn du hier den anderen Konstruktor von Dreieck aufrufst brauchst du ihn nicht mehr.
    etwa so.

    class gleichseitigesDreieck : public Dreieck
    {
       float a;
       public:
       gleichseitigesDreieck(float seite) : Dreieck(seite, seite/2.0*sqrt(3.0)), a(seite) {}
       void Daten();
       void Flaeche();
    };
    


  • Dima schrieb:

    1. Wieso wird unbedingt der Default Konstruktor in der Klasse Dreieck benötigt?
    Bei weglassen gibt es compilerfehler.

    1. Weil "gleichseitigesDreieck" keinen anderen in der Initialisierungsliste aufruft.

    gleichseitigesDreieck(float seite)
    : Dreieck(xxx, yyy), // <-- xxx,yyy entsprechend austauschen
      a(seite)
    {}
    

    2. Man schreibt man auch den Fehler dazu, die funktionierenden Glaskugeln sind leider Mangelware.
    3. gleichseitigesDreieck ist eh ein Designfehler. es währe besser der Klasse Dreieck eine Methode alle isGleichseitig zu verpassen. Den nach dem Design ist ein gleichseitigesDreieck zwar ein Dreieck, aber ein Dreieck niemals gleichseitig.

    Dima schrieb:

    Die Klasse Figur ist eine Abstrakte Klasse mit den beiden Virtuellen Methoden Flaeche() und Daten(), macht es Sinn die beiden Methoden in der Basisklasse Dreieck auch virtuell anzulegen? Wenn ja welche Vor/Nachteile hat man dann davon.

    Es macht keinen Unterschied da "virtual" quasi mitvererbt wird.

    cu André



  • Ok, habe vergessen zu erläutern, dass das Problem in der folgenden Zeile liegt.
    Es spielt im Moment keine Rolle, wie die klasse verwendet wird.

    gleichseitigesDreieck(float seite) : a(seite) {}
    

    ich möchte bloß, dass in der klasse Dreieck das Objekt mit zwei variablen instantiiert wird und in der klasse gleichseitigesDreieck nur mit einer Variable.
    Beim weglassen des Standardkonstruktors in der basisklasse bringt mir der compiler einen fehler, dass er den zugehörigen konstruktor in der Basisklasse nicht findet.
    Ich muss entweder den Standardkonstruktor oder ich einen neuen anlegen, der mir die variablen der Basisklasse benutzt:

    Dreieck(float grundseite) : g(grundseite) {}
    

    Dann habe ich das Problem, dass beim erstellen von Objekten der Klasse Dreieck so etwas zulässig ist:
    [cpp]
    Dreieck dreieck1(); //soll nicht sein
    Dreieck dreieck2(1); //soll auch nicht sein
    gleichseitigesDreieck dreieck3(2); //ok
    Dreieck dreieck4(2,3); //ok

    MfG
    Dima



  • Warum machst dus dann nicht einfach so wie ich dir das gezeigt habe?



  • Braunstein schrieb:

    Warum machst dus dann nicht einfach so wie ich dir das gezeigt habe?

    Sorry, habe ein wenig länger gebraucht um den Beitrag zu Verfassen. Danke für euere unterstützung.
    Bin nicht so flink mit den Fingern. 🙂

    d.h. Nur fürs verständnis. Man braucht, wenn Variablen in der unterklasse initialisiert werden, einen passenden kostruktor in der Basisklasse???

    2. Habt ihr etwas mehr info material Links,etc. für das stichwort virtuell? raff es einfach nicht. 😞



  • Das ist komisch formuliert und trifft es irgendwie immer. Natürlich braucht die Basisklasse den Konstruktor, den Du in der Unterklasse aufrufst. Der springende Punkt ist, du musst im Konstruktor jeder abgeleiteten Klasse (irgend)einen Konstruktor der Basisklasse explizit aufrufen, wenn die Basisklasse keinen Default-Konstruktor (den ohne Parameter) hat.





  • LordJaxom schrieb:

    Das ist komisch formuliert und trifft es irgendwie immer. Natürlich braucht die Basisklasse den Konstruktor, den Du in der Unterklasse aufrufst.Der springende Punkt ist, du musst im Konstruktor jeder abgeleiteten Klasse (irgend)einen Konstruktor der Basisklasse explizit aufrufen, wenn die Basisklasse keinen Default-Konstruktor (den ohne Parameter) hat.

    Jetzt fehlt mir zum verständnis das warum?. Wieso muss ich den explizit einen konstruktor in der Basisklasse aufrufen? Ich habe ja einen Konstruktor in der abgeleiteten Klasse erstellt und möchte nur die Variablen der abgeleiteten klasse initialisieren. Um die Vererbten Variablen zu initialisieren würde es mir einleuchten, dass man den konstruktor der Basisklasse benötigt, aber warum auch für die Variableninitialisierung nur in der abgeleiteten klasse? 😕

    Braunstein schrieb:

    zu virtual
    http://tutorial.schornboeck.net/virtual.htm
    http://www.ica1.uni-stuttgart.de/Courses_and_Lectures/C++/script/node18.html#SECTION00422000000000000000

    Danke für die links werde sie mir gleich duch den Schädel ziehen. 😉



  • Dima schrieb:

    Jetzt fehlt mir zum verständnis das warum?. Wieso muss ich den explizit einen konstruktor in der Basisklasse aufrufen? Ich habe ja einen Konstruktor in der abgeleiteten Klasse erstellt und möchte nur die Variablen der abgeleiteten klasse initialisieren. Um die Vererbten Variablen zu initialisieren würde es mir einleuchten, dass man den konstruktor der Basisklasse benötigt, aber warum auch für die Variableninitialisierung nur in der abgeleiteten klasse? 😕

    Die Basisklasse ist doch immer Teil der abgeleiteten, so musst Du den Basisklassenanteil auch immer konstruieren (lassen). Und das macht nunmal der Konstruktor der Basisklasse. Du kommst in der abgeleiteten ja auch garnicht an die private-Member der Basisklasse ran.



  • LordJaxom schrieb:

    Die Basisklasse ist doch immer Teil der abgeleiteten, so musst Du den Basisklassenanteil auch immer konstruieren (lassen). Und das macht nunmal der Konstruktor der Basisklasse.

    Das ergibt Sinn. Danke.

    LordJaxom schrieb:

    Du kommst in der abgeleiteten ja auch garnicht an die private-Member der Basisklasse ran.

    Ja schon, aber an die protected.

    MfG
    Dima


  • Mod

    Dima schrieb:

    Jetzt fehlt mir zum verständnis das warum?. Wieso muss ich den explizit einen konstruktor in der Basisklasse aufrufen? Ich habe ja einen Konstruktor in der abgeleiteten Klasse erstellt und möchte nur die Variablen der abgeleiteten klasse initialisieren. Um die Vererbten Variablen zu initialisieren würde es mir einleuchten, dass man den konstruktor der Basisklasse benötigt, aber warum auch für die Variableninitialisierung nur in der abgeleiteten klasse?

    Weil ein Konstruktor immer alle nichtstatischen Member (und dazu zählen mögliche Basisklasse) initialisiert, wenn diese keine PODs sind. PODs werden nur initialisiert, wenn sie explizit in der Initialisierungsliste auftauchen.

    Deine Verwirrung ist nicht ganz unberechtigt, allerdings liegt das Problem an anderer Stelle. Ein (Objekt des Typs) gleichseitigesDreieck ist kein Dreieck. Generell (99:1) ist es ein Fehler, von konkreten Klassen (solche, die selbst vollständige Objekte erstellen) öffentlich abzuleiten.



  • asc schrieb:

    gleichseitigesDreieck(float seite)
    : Dreieck(xxx, yyy), // <-- xxx,yyy entsprechend austauschen
      a(seite)
    {}
    

    Das sieht irgenwie getrickst aus. Gibt es keine andere möglichkeit?
    Den das Dreieck(x, y) benötig man ja gar net. Denn die Variablen von Dreieck will ich nicht benutzen.



  • Da sind sie deswegen trotzdem. Genau sowas verrät dir meist einen Designfehler.
    Du könntest gleichseitigesDreieck ja auch von Figur ableiten. Dann hast du das Problem nicht.


  • Mod

    Gegenwärtig hast du:

    Figur
               |
            Dreieck
               |
    gleichseitigesDreieck
    

    besser ist:

    Figur
               |
         AbstractDreieck
          /         \
    Dreieck      gleichseitigesDreieck
    

    AbstractDreieck stellt dabei nur das gemeinsame Interface dar.



  • camper schrieb:

    Weil ein Konstruktor immer alle nichtstatischen Member (und dazu zählen mögliche Basisklasse) initialisiert, wenn diese keine PODs sind. PODs werden nur initialisiert, wenn sie explizit in der Initialisierungsliste auftauchen.

    Deine Verwirrung ist nicht ganz unberechtigt, allerdings liegt das Problem an anderer Stelle. Ein (Objekt des Typs) gleichseitigesDreieck ist kein Dreieck. Generell (99:1) ist es ein Fehler, von konkreten Klassen (solche, die selbst vollständige Objekte erstellen) öffentlich abzuleiten.

    Ja ein design fehler ist es alle mal, aber die Aufgabenstellung verlangt es nunmal das man von einem Rechteck ein Quadrat ableiten soll. Oder von Dreieck einen Gleichseitigen und Gleichschenkligen ableiten müsse. Deswegen kommen mir auch solche Fragen hoch. Aber immer positiv sehen aus fehlern anderer lern man auch. 😃



  • camper schrieb:

    Weil ein Konstruktor immer alle nichtstatischen Member (und dazu zählen mögliche Basisklasse) initialisiert, wenn diese keine PODs sind. PODs werden nur initialisiert, wenn sie explizit in der Initialisierungsliste auftauchen.

    Sorry POD ???



  • camper schrieb:

    ...PODs werden nur initialisiert, wenn sie explizit in der Initialisierungsliste auftauchen....

    😮 😕
    Echt ?

    struct A {
       int a, b;
       A() : a(1), b(2) {}
    };
    
    struct B : A {
    };
    
    int main() {
       B b;
    

    Wird hier nicht A::A() aufgerufen ?
    Oder ist A kein POD ?

    Gruß,

    Simon2.



  • Du wartest jetzt zwar sicher auf camper, 🙂 aber:
    Eine struct mit Konstruktor ist kein POD mehr
    @Dima
    POD (Plain old data).



  • Dima schrieb:

    Das sieht irgenwie getrickst aus. Gibt es keine andere möglichkeit?
    Den das Dreieck(x, y) benötig man ja gar net. Denn die Variablen von Dreieck will ich nicht benutzen.

    Du hast hierbei ein Deckproblem, hierzu rollen wir mal alles auf (Nur bezogen auf den Regelfall, nicht auf solche Spezialitäten wir private Ableitungen...):

    Ja, umgangssprachlich kann man sagen das ein gleichseitigesDreieck ein Dreieck ist. Vererbung setzt man ein um Anzuzeigen, das es sich um ein spezialisiertere Form handelt, nicht aber für Einschränkungen. Alles was die Basisklasse als Schnittstelle unterstützt muss auch die abgeleitete Klasse unterstüzen. Die abgeleitete Klasse "ist ein" auch ein Element der Basisklasse.

    Einschränkungen stellt man besser durch Eigenschaften dar, als durch Ableitungen. Ich schätze mal du wirst es an einen extremen Beispiel verstehen:

    class Dreieck;
    class BlauesDreieck : public Dreieck;
    

    Ich hoffe mal dir leuchtet ein, das sowas nicht sinnvoll ist.

    So, da eine Abgeleitete Klasse nun alles was die Basisklassen repräsentieren auch darstellt, muss es auch alles initialisieren was dazu gehört. Dazu müssen passende Konstruktoren der Basisklasse aufgerufen werden. Falls es kein Standardkonstruktor gibt, musst du einen spezialisierten in der Initialisieungsliste aufrufen.

    Was aber grundsätzlich geht: Du kannst einen Konstruktor auch protected deklarieren, so das er von Außerhalb nicht zugreifbar ist. Aber auch das verbessert kein schlechtes Design.

    Und zu guter letzt: Vererbung lässt sich auch durch Komposition ersetzen. Bei Komposition enthält deine Klasse eine andere, gibt aber nur seine Schnittstelle frei. Du könntest z.B. sagen du schreibst eine Klasse gleichseitigesDreieck, da aber die Regeln für ein Dreieck nicht vollständig zutreffen leitest du nicht ab, sondern schreibst eine passende Schnittstelle. Bei der Implementierung kannst du aber auf das Dreieck zurückgreifen, und nimmst daher ein Dreieck als Member.

    Noch immer kein wirklich gutes Design, aber besser als die Vererbung.

    cu André


Anmelden zum Antworten