Destruktoren- Klarrheit schaffen



  • 3. Das heißt alles was ich im Konstruktor als Zeiger erzeuge, muss ich im Destruktor wieder löschen. Aber dass meine Klasse "Class" Zeiger als Variablen enthält, stört nicht?! Die muss ich nicht zusätzlich im Destruktor löschen?



  • Alles was deine Klasse an Speicher alloziert oder zu dem sie das Lifetime-Management übernimmt und zu dem sie das Lifetime-Management nicht wieder abgibt (nach außen oder in smartpointer oder pointer-container etc.), muss deine Klasse wieder freigeben. Wer sollte das denn auch sonst übernehmen?



  • Du "musst" gar nichts. Es wäre aber extrem praktisch, wenn die Klasse, bzw die Objekte der Klasse "sich selbst verwalten würden". Schau Dir std::string an. Du kannst std::string so wie int oder double auch verwenden, was das Kopieren und Zuweisen angeht. Du musst Dich bei der Benutzung von std::string nicht mehr selbst um die Veraltung von eventuell dynamisch alloziertem Speicher kümmern. Das macht std::string schon von ganz alleine. Und das ist auch der Witz an C++: Du kannst eigene Typen erzeugen, die leicht benutzbar sind und gewisse Resourcen selbst verwalten können. Es gibt aber nicht "den einen richtigen Weg".

    Und da ich eben ein Beispiel gesehen habe, was die Dreierregel verletzt, möchte ich auch nochmal darauf hinweisen: DREIERREGEL.



  • class A;
    class B;
    class C;
    class D;
    class Class
    {
    private:
        string Name;
    public:
        map<string, A*> test_map;
        B* pB;
        bool con;
        C instC;
        D& rD;
    
        int i;
        void f()
        {
            inst->do;        
        }
    
        const string& GetName() const
        {
            return Name;
        }
        void SetName(const string& NameNew)
        {
            Name = NameNew;  
        }
        ~Class();  //Inhalt???
    };
    

    Wie würde der Destruktor aussehen, wenn der Konstruktor folgender ist?
    (1):

    Class()
    {
        con = true;
        pB = new B();
        instC = C();
        Name = "Test";
    }
    

    (2)

    Class()
    {
    }
    


  • Class:~Class()
    {
        delete pB;
    }
    

    referenzen müssen immer initialisiert werden, die können nicht auf nichts zeigen
    und da greift die initialisierungsliste...



  • Gar nicht. Du initialisierst den Referenzmember nicht. Deshalb würde der Code so nicht compilieren.
    Grundsätzlich: Jede Ressource die Du anforderst, und welche eine explizite Freigabe erfordert, muss auch von Dir wieder freigegeben werden. Wo die Freigabe durchgeführt wird ist letzten Endes egal, jedoch bietet sich der Destruktor von Klassen hier förmlich an.
    Guck Dir auch mal RAII an. Das ist ein sehr wichtiges Konzept in C++ um effektiv Ressourcenlecks zu vermeiden.



  • Ich habe noch nie mit Referenzen als Member gearbeitet, ich wollte nur möglichst alles mal abdecken ;-).

    Aber jetzt weiß ich immer noch nicht, ob ich delete pB in beiden Destruktoren oder nur im ersten Destruktor hizufügen muss... Aber ich schätze in beiden, denn irgendwann wird ja der Zeiger initialisiert und muss dann gelöscht werden.

    Das heißt zu der Erstellung meines Destruktors gehe ich alle meine Zeiger Member durch und lösche diese im Destruktor?!



  • Na im ersten Fall allozierst du Speicher für ein Objekt (int) und im zweiten nicht. Würdest du jetzt im 2. Fall Speicher freigeben (und ggf. Destruktoren aufrufen), den du nie allozierst hat? Nein? Ich auch nicht!

    delete ist nicht dafür da, um "Zeiger" zu löschen. Delete ist dafür da, um Objekte in alloziertem Speicher (per new) zu zerstören und den Speicher wieder freizugeben. Die Adresse dieses Speicherbereichs kann natürlich in einem Zeiger stecken.



  • deshalb macht es auch Sinn pointer (sofern sie nicht bei Erstellung des Objects mit new verwurstet werden) erstmal auf 0 zu setzen. bei delete pointer und pointer==0 passiert nämlich nix.

    class Un 
    {
      int * maybe;
    public:
      Un(int i) : maybe(new int(i)) {}
      Un() : maybe(0) {}
      ~Un() { delete maybe; }
    
      void sinn() { if (!maybe) maybe = new int(12345); }
    };
    


  • Es gibt nur einen Destruktor pro Klasse. Ob du alle Zeiger deines Objektes löschen musst hängt davon ab, ob das Objekt diesen Zeiger auch besitzt. Als Faustregel (wurde ja auch schon erwähnt) sollte jedes Objekt, das in seinem Konstruktor dynamischen Speicher anfordert, als Besitzer gelten und damit auch für die korrekte Freigabe verantwortlich sein.

    class ClassA
    {
       ...
    };
    
    class ClassB
    {
       // kein Besitz, nur Zeiger auf ein anderes Objekt vom Typ Class A
       ClassA* data1_;      
    
       // echter Besitz des dynamisch erzeugten Objektes
       ClassA* data2_;
    
    public:
       ClassB( ClassA* d1 ) :
          data1_( d1 ),
          data2_( new ClassA() )
    
       ~ClassA()
       {
          delete data2_;
       }
    
    private:
       // Kopien verbieten
       ClassB( const ClassB& other );
       ClassB& operator=( const ClassB& other );
    };
    
    int main()
    {
       ClassA ca;
       ClassB cb( &ca );
    }
    

    In diesem Beispiel hat ClassB zwei Zeiger auf jeweils ein Objekt vom Typ ClassA, einer davon wird von aussen bereitgestellt, der andere innerhalb des Konstruktors dynamisch erzeugt. Das Objekt cb besitzt nur das Objekte, auf das data2_ zeigt und kann data1_ im Destruktor ignorieren. Für data2_ ist es aber verantwortlich und muss daher dafür sorgen, dass es im Destruktor durch einen Aufruf von delete korrekt abgeräumt wird.



  • Ok also alle zeiger für die ich innerhalb der Klasse "new" aufrufe, sollte ich in der Regel im Destruktor löschen?!

    Nun kommt es aber noch etwas komplizierter:

    class A;
    class Class
    {
    private:
        string Name;
    public:
        map<string, A*> testmap;
        A* pA;
        string na;
        void f()
        {
            na = "Nr.";
            for(i=0; i<5; i++)
            {
                na = na + "I"
                pA = new A();
                pA->Name = na;
                testmap.insert(pair<string, A*>(pA->eName, pA));
            }   
        }
    
        Class()
        {
             pA = new A();
             pA->Name = "test";
             testmap.insert(pair<string, A*>(pA->eName, pA));
        }
        ~Class();  //Inhalt???
    };
    


  • Am Prinzip hat sich nichts geändert, nur dass du keine benannten Variablen mehr hast, sondern dynamisch erzeugte Objekte in einem Container speicherst. Die Klasse Class ist für das Löschen dieser Objekte verantwortlich und sollte das im Destruktor auch machen.



  • pA sollte hier keine Membervariable sein sondern lokal in f und im Konstruktor.
    Dann könnte der Destruktor so aussehen.

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


  • Wieso muss dafür pA lokal sein? Kann ich den Destruktor nicht auch so verwenden?



  • Niemand hat gesagt muss. Da du die per pA referenzierten Objekte aber überhaupt nur im Konstruktor und f lokal verwendest, hättest du die A-Variable auch als lokale ausführen können (deshalb sprach mein Vorredner von "sollte").



  • Wenn du das nicht tust, dann muss du in f() noch den Speicherbereich, auf den pA bis dahin zeigt freigeben und ihm dann erst einen neuen zuweisen. Im Moment hast du dort ein Speicherleck.



  • Da ist kein Speicherleck wenn im Destruktor die map ordentlich abgeräumt wird.
    pA als Member kann hier schlicht Verwirrung stiften. Dann könnte leicht der Fall eintreten, dass man ein Objekt versucht zweimal zu löschen.



  • Das kann ich nun nicht ganz nach vollziehen. Wieso muss ich den Speicherbereich, auf den pA vorher zeigt jedes mal freigeben? Jeder dieser Speicherbereiche wird doch noch in meiner map gebraucht/ verwaltet.

    Ich habe auch versucht, pA lokal zu definieren. Aber leider scheint meine map, wenn ich sie dann irgendwo benutze, leer zu sein. Nur wenn pA global in der Klasse definiert ist, kann ich die map später benutzen...



  • Wie hast du dann pA lokal benutzt?



  • Ahh, siehste, ich hab nur bis zum pA->eName gelesen. Na, dann scheinst du es doch jetzt verstanden zu haben^^

    edit: Wobei du pA natürlich lokal definieren könntest in den Funktionen, gibt ja keinen Grund diesen "temporär"-Zeiger als Zustand des Objekts zu betrachten, es sei denn, du brauchst immer einen Zeiger auf das zuletzt eingefügte Objekt!


Anmelden zum Antworten