Fragen zum dynamischen Erzeugen von Objekten



  • Ich habe ein, zwei kleinere Verständnissprobleme mit C++.
    Ich vereinfache mein Beispiel mal:

    Ich habe eine Klasse "Schueler":

    class Schueler
    {
    protected:
    	std::string Name;
    	int Alter;
    public:
    	Schueler (std::string name, int alter) : Name(name), Alter(alter) { /* empty */ }
    	~Schueler();
    	std::string getName(void);
    };
    

    Davon abgeleitet habe ich zwei Klassen, "Grundschueler" und "Hauptschueler":

    class Grundschueler : Schueler
    {
    	/* Grundschueler Attribute */
    public:
    	Grundschueler (std::string name, int alter);
    	~Grundschueler();
    };
    
    class Hauptschueler : Schueler
    {
    	/* Hauptschueler Attribute */
    public:
    	Hauptschueler (std::string name, int alter);
    	~Hauptschueler();
    };
    

    Außerdem gibt es eine Schule, diese "besitzt" mehrere Schüler:

    class Schule
    {
    	std::map<std::string, Schueler*> schueler;
    public:
    	Schule (std::string name);
    	~Schule();	
    	void addSchueler(std::string name, int Alter);
    };
    

    Dazu jetzt also paar Fragen:

    1. die Methode "addSchueler()" entscheidet, ob der neue Schüler ein Grund- oder Hauptschüler ist und erstellt diesen mittels "new".
      Der resultierende Zeiger soll in die "schueler"-Map abgelegt werden. Allerdings meckert hier der Compiler: anscheinend kann der Container wirklich nur Schueler aufnehmen, allerdings keine davon abgeleiteten Klassen. Verwende ich bei der Deklaration der Kinder-Klassen das Keywort "public" bei der Vererbung klappt es. Wieso?
      Ich dachte, dieses Keywort entscheidet nur über die Sichtbarkeit von Membern. Aber ein Grundschueler ist doch ein Schueler, Sichtbarkeit hin oder her, und müsste sich doch in den Container packen lassen... Zumindest dachte ich das.

    2. Wird die Schule zerstört entsteht natürlich ein Speicherleck!
      Also müssen im Destruktor von "Schule" erstmal alle Schueler zerstört werden. Dafür iteriere ich über die Map und lösche jeden Eintrag von Hand:

    Schule::~Schule()
    {
    	std::map<std::string, Schueler*>::iterator it;
    
    	for (std::map<std::string, Schueler*>::iterator it=schueler.begin(); it!=schueler.end(); ++it)
    		delete (it->second);
    }
    

    Passt das so oder macht man das anders? Gibts da schönere Ansätze?

    1. Diese Frage bezieht sich eigentlich auf meine zweite.
      Da der Container eigentlich nur Schueler aufnimmt wird beim delete-Aufruf jeweils nur der Schueler-Destruktor ausgeführt. In Wahrheit befinden sich im Container aber Grundschueler und Hauptschueler! D.h. bei jedem delete-Aufruf müssten eigentlich zwei Destruktoren ausgeführt werden (erst vom jeweiligen Kind, dann von "Schueler").
      Wie löse ich das? Casten? Wenn ja, woher bekomme ich den genauen Typ zu dem gecastet werden muss?

    Viele Grüße!



  • Grundsätzlich fällt mir auf, dass zumindest der ctor virtual sein sollte, damit der "richtige" aufgerufen wird.

    Ansonsten hier weiteres zur Vererbung
    http://de.wikibooks.org/wiki/C++-Programmierung:_Vererbung
    siehe dort unter:

    Zugriffskontrolle

    Die Vererbungsart zeigt an, ob beim Vererben der Zugriff auf Elemente der Basisklasse eingeschränkt wird. Sie wird vor dem Namen der Basisklasse angegeben. Wie bei Memberdeklarationen gibt es die Schlüsselwörter public, protected und private (Standard-Vererbungsart). Die Deklarationen

    class A { /* ... / };
    class B : public A { /
    ... / };
    class C : protected A { /
    ... / };
    class D : private A { /
    ... / }; // (oder (Standard-Vererbungsart): "class D : A { / ... */ };")
    bewirken folgendes:

    Ist ein Element in A public protected private
    ... wird es in B public protected unzugänglich
    ... wird es in C protected protected unzugänglich
    ... wird es in D private private unzugänglich

    Beachten Sie, dass friend-Beziehungen nicht vererbt werden.



  • Nimm Smart Pointer, dann hast du mit dem delete kein Problem mehr.



  • Helmut.Jakoby schrieb:

    Grundsätzlich fällt mir auf, dass zumindest der ctor virtual sein sollte, damit der "richtige" aufgerufen wird.

    Nein, falsch. Konstruktoren können niemals virtuell sein, was du wahrscheinlich meinst ist der Destruktor. Wennd er virtuell ist wird "automatisch" der Unterklassendestruktor aufgerufen

    Helmut.Jakoby schrieb:

    Ansonsten hier weiteres zur Vererbung
    http://de.wikibooks.org/wiki/C++-Programmierung:_Vererbung
    siehe dort unter:
    [...]
    Beachten Sie, dass friend-Beziehungen nicht vererbt werden.

    Vererbung ist ein Konzept mit dem Beziehungen ausgedrückt werden können.
    Öffentliche vererbung (und damit eigentlich, was der Volksmund darunter versteht) ist: Derive erbt öffentlich von Base --> Derive ist ein Base

    Scott Meyers geht in seinem Effective C++ hin und formuliert private Vererbung umgangssprachlich:
    A erbt privat von B --> A ist mithilfe von B implementiert.
    Der grund dafür ist, dass A zwar alles öffentliche von B sieht und darauf zugreifen kann (es also nutzen kann), aber es wird nicht nach außen von A propagiert.

    Protected Inheritance ist mir noch nie untergekommen.

    Zu deiner weiteren Frage:
    Die Zeiger in der Map besitzen die Schueler-Objekte, sind also dafür zuständig sie zu löschen (und auch mithilfe der add Methode zu initialisieren). Da dabei öfters mal was scheifgehen kann, nutzt man das RAII Konzept. Da googlest du am Besten mal nach. (Für manch einen ist genau dieses Konzept der Grund, warum C++ so geil ist und was es von den meisten anderen Sprachen abhebt).

    Kurzum, nutze sogenannte Smart-Pointer:

    class Schule
    {
        std::map<std::string, std::unique_ptr<Schueler>> schueler;
    public:
        Schule (std::string name);
        virtual ~Schule() {} // reicht hier jetzt ;) 
        void addSchueler(std::string name, int Alter)
        {
             // ...
             schueler[name] = std::make_unique<Hauptschueler>(name, alter); // bin mir bei der Syntax von make_unique nicht ganz sicher, sollte aber in etwa hinkommen
            // ...
        }
    };
    

    Der unique_ptr löscht seinen pointee am Ende automatisch und du kannst es nicht nicht vergessen.



  • Helmut.Jakoby schrieb:

    Grundsätzlich fällt mir auf, dass zumindest der ctor virtual sein sollte, damit der "richtige" aufgerufen wird.

    Ansonsten hier weiteres zur Vererbung
    http://de.wikibooks.org/wiki/C++-Programmierung:_Vererbung
    siehe dort unter:

    Zugriffskontrolle

    Die Vererbungsart zeigt an, ob beim Vererben der Zugriff auf Elemente der Basisklasse eingeschränkt wird. Sie wird vor dem Namen der Basisklasse angegeben. Wie bei Memberdeklarationen gibt es die Schlüsselwörter public, protected und private (Standard-Vererbungsart). Die Deklarationen

    class A { /* ... / };
    class B : public A { /
    ... / };
    class C : protected A { /
    ... / };
    class D : private A { /
    ... / }; // (oder (Standard-Vererbungsart): "class D : A { / ... */ };")
    bewirken folgendes:

    Ist ein Element in A public protected private
    ... wird es in B public protected unzugänglich
    ... wird es in C protected protected unzugänglich
    ... wird es in D private private unzugänglich

    Beachten Sie, dass friend-Beziehungen nicht vererbt werden.

    Okok, aber inwiefern beantwortet das eine meiner Fragen?
    In Frage 1) schreibe ich doch dass mir grundsätzlich klar ist, was es mit Vererbung und Sichtbarkeit auf sich hat.
    Aber wieso nimmt ein Container wie std::map<Schueler> keine "Grundschueler" auf, außer ich mache eine "public" Vererbung?



  • lugge86 schrieb:

    Aber wieso nimmt ein Container wie std::map<Schueler> keine "Grundschueler" auf, außer ich mache eine "public" Vererbung?

    Weil die Information, dass ein "Hauptschueler" ein "Schueler" *ist*, eben nur bei public Vererbung "nach außen" bekannt ist.

    Bei Vererbung braucht man i.d.R. public-Vererbung. Ganz selten mal private (in Sonderfällen, meistens ist Komposition die bessere Wahl).



  • Vielen Dank dafür.
    Find ich auf den ersten Blick ungewohnt (hab geringe Java-Vorkenntnisse und eine gute Portion C). Dachte das würde sich wirklich nur auf die Sichtbarkeit von Membern beziehen.

    Ich lese mich zwar gerade in SmartPointer ein.
    Aber aus Interesse: Angenommen ich würde im Desktruktor meiner "Schule", so wie oben gezeigt, alle Grund- und Hauptschueler per Hand löschen indem ich durch die Map iteriere.

    Wie stelle ich bei diesem Vorgehen sicher, dass nicht nur der "Schueler"-Destruktor aufgerufen wird, sondern davor auch der jeweilige "Unterdekonstruktor"?



  • Wenn

    Base* b = new Derived();
    

    gehen würde, wäre private Vererbung völlig sinnlos, da du mit

    Base* b = new Derived();
    b->SomethingThatIsPublicInBase();
    

    den Zugriffsschutz den du durch private ja haben willst (sonst würdest du ja nicht private schreiben) jederzeit umgehen könntest, und sogar ganz versehentlich oft würdest.



  • kleiner Troll schrieb:

    Wenn

    Base* b = new Derived();
    

    gehen würde, wäre private Vererbung völlig sinnlos, da du mit

    Base* b = new Derived();
    b->SomethingThatIsPublicInBase();
    

    den Zugriffsschutz den du durch private ja haben willst (sonst würdest du ja nicht private schreiben) jederzeit umgehen könntest, und sogar ganz versehentlich oft würdest.

    Vielen Dank, so macht das natürlich Sinn.
    Aber bei public-Vererbung ist mein Vorgehen ja durchaus legitim, oder? Zumindest kompilierts und funktionierts 🙂



  • lugge86 schrieb:

    Vielen Dank dafür.
    Find ich auf den ersten Blick ungewohnt (hab geringe Java-Vorkenntnisse und eine gute Portion C). Dachte das würde sich wirklich nur auf die Sichtbarkeit von Membern beziehen.

    Ich lese mich zwar gerade in SmartPointer ein.
    Aber aus Interesse: Angenommen ich würde im Desktruktor meiner "Schule", so wie oben gezeigt, alle Grund- und Hauptschueler per Hand löschen indem ich durch die Map iteriere.

    Wie stelle ich bei diesem Vorgehen sicher, dass nicht nur der "Schueler"-Destruktor aufgerufen wird, sondern davor auch der jeweilige "Unterdekonstruktor"?

    Wie gesagt, du machst den Destruktor von Base virtuell. In meinem zitierten Beispiel oben stehts auch drin.

    In Java ist es genau anders herum als in C++. Du kannst erstmal jede Methode überschreiben, ausser du verbietest es per final. In C++ kannst du erstmal nix überschreiben (maximal nur überdecken), es sei denn du machst die entsprechende Methode virtual.



  • kleiner Troll schrieb:

    Wenn

    Base* b = new Derived();
    

    gehen würde, wäre private Vererbung völlig sinnlos, da du mit

    Base* b = new Derived();
    b->SomethingThatIsPublicInBase();
    

    den Zugriffsschutz den du durch private ja haben willst (sonst würdest du ja nicht private schreiben) jederzeit umgehen könntest, und sogar ganz versehentlich oft würdest.

    Du musst natürlich(*) casten...

    Base* b = (Base*)new Derived;
    b->SomethingThatIsPublicInBase();
    

    (*) natürlich wie in: "Natürlich ist's in C++ nicht so einfach, wie man denkt."



  • Muss ich? Das verwundert mich etwas, oder versteh ich dich grade falsch?

    class Foo {};
    class Bar : public Foo {};
    
    int main()
    {
       Foo* f = new Bar();
    }
    

    Kompiliert, wie ich auch erwarten würde, einwandfrei. (Bis auf eine Warnung das f nicht benutzt wird)

    @Lugge
    Das ist prinzipiell so ok. Es gibt noch ein paar Fallstricke, in C++ gibt's mit Vererbungs einige eigenarten, die zwar alle ihren Sinn haben aber überraschend sein können. Der virtuelle Destruktor ist wichtig (einfach in virtual ~Schueler();)



  • kleiner Troll schrieb:

    Muss ich? Das verwundert mich etwas, oder versteh ich dich grade falsch?

    Wohl schon.

    Musst Du natürlich nur, wenn Du an die public member einer privat(!) vererbten Klasse ranwillst. Bzw. einen Zeiger auf eine eigentlich "inaccessible base class" brauchst. Implizite Konvertierung zieht hier ja nicht - was ja der Aufhänger des Threads ist.
    :

    struct Base{
      void SomethingThatIsPublicInBase(){}
      virtual ~Base(){}
    };
    struct Derived : private Base{};
    
    int main(){
      Base* b = (Base*)new Derived;
      b->SomethingThatIsPublicInBase(); // !
      delete b;
    }
    

    Disclaimer: natürlich propagiere ich diesen Weg nicht! 🙂



  • kleiner Troll schrieb:

    Der virtuelle Destruktor ist wichtig (einfach in virtual ~Schueler();)

    Ja, habs mittlerweile auch umgeändert und es klappt.

    Jetzt werd ich das mal auf SmartPointer umstellen.
    Nur, was ich bisher herausgefunden habe: dieses oben genannte make_unique kommt erst in C++14 🙂



  • Dann hab ich dich tatsächlich falsch verstanden, du mich aber wohl auch 😉
    Es ging mir ja darum, warum es nicht ohne "tricks" geht das einfach so zuzuweisen.
    Mit dem guten alten "Mein lieber Compiler, halt die Klappe und tu was ich sage"-casts kann man das dann wieder schon machen, sollte man aber natürlich nicht. 🙂



  • lugge86 schrieb:

    kleiner Troll schrieb:

    Der virtuelle Destruktor ist wichtig (einfach in virtual ~Schueler();)

    Ja, habs mittlerweile auch umgeändert und es klappt.

    Jetzt werd ich das mal auf SmartPointer umstellen.
    Nur, was ich bisher herausgefunden habe: dieses oben genannte make_unique kommt erst in C++14 🙂

    Welchen Compiler nutzt du denn? Wenn du irgendwas C++11 fähiges nimmst, dann kannst du dir make_unique leicht nachbauen. Bei älteren Compilern ist das nicht mehr so trivial, aber da kannst du dir eine kleine spezialisierte Funktion schnell schreiben. oder du machst es auf herkömmliche Art und Weise und schiebst einen unique_ptr rein, den du on-the-fly oder vorher initialisierst.



  • Soo, ich hab leider noch was:

    Wie gesagt, ich sammle meine Grundschueler und Hauptschueler in einer map<Schueler*>

    Wenn ich durch die Map iteriere würde ich gerne feststellen, welchen Types denn der aktuelle Zeiger ist.
    Ich versuche es mit typeid:

    if (typeid(it->first) == typeid(Grundschueler*))
        cout << "Grundschueler" << endl;
    

    Leider schlägt der Vergleich nie an. An was liegt das?
    Kann ich überhaupt anhand des Zeigers aus der Map feststellen, welchen Typ das aktuelle Element hat?



  • lugge86 schrieb:

    Kann ich überhaupt anhand des Zeigers aus der Map feststellen, welchen Typ das aktuelle Element hat?

    Du kannst natürlich per dynamic_cast casten und prüfen, ob der Rückgabewert != NULL ist.

    Allerdings: wofür brauchst du das? Spezifisches Verhalten sollte mittels virtueller Methoden implementiert werden.


  • Mod

    Wenn ich durch die Map iteriere würde ich gerne feststellen, welchen Types denn der aktuelle Zeiger ist.

    Wenn du so etwas wissen möchtest, dann gehören die beiden Typen nicht in eine gemeinsame Liste. Benutz virtuelle Methoden.



  • lugge86 schrieb:

    if (typeid(it->first) == typeid(Grundschueler*))
        cout << "Grundschueler" << endl;
    

    Leider schlägt der Vergleich nie an. An was liegt das?
    Kann ich überhaupt anhand des Zeigers aus der Map feststellen, welchen Typ das aktuelle Element hat?

    Wenn du deine map nicht verändert hast(sie also noch eine map<string, schueler*> ist), dann ist das falsch.

    Du fragst, ob die typeid von einem String gleich der eines X-Schülers ist. Nein, niemals. Nimm nicht it->first, sondern it->second


Log in to reply